Frontend Setup
Set up Velocity's frontend stack with Inertia.js, React, TypeScript, and Vite for hot-reload development.
Velocity’s frontend stack combines Go, Inertia.js, React, TypeScript, and Vite for a modern development experience.
How It Works
The frontend stack combines four technologies:
- Go Backend - Handles routing, handlers, and data
- Inertia.js - Bridge between Go and React (no API endpoints needed)
- React + TypeScript - Type-safe frontend components
- Vite - Fast development server and production bundling
The flow works like this:
- User visits
/posts - Go router calls
PostHandler.Index() - Handler fetches data and calls
view.Render("Posts/Index", props) - Inertia sends props to React component at
resources/js/pages/Posts/Index.tsx - React renders the page with full SPA navigation
No REST API, no GraphQL, no separate frontend routing. Just handlers and components.
Project Structure
your-app/
├── app/
│ └── handlers/ # Go handlers
├── resources/
│ ├── js/
│ │ ├── app.tsx # React entry point
│ │ ├── pages/ # Page components (mapped to routes)
│ │ ├── components/ # Reusable UI components
│ │ ├── layouts/ # Layout wrappers
│ │ └── hooks/ # Custom React hooks
│ ├── css/
│ │ └── app.css # Tailwind CSS
│ └── views/
│ └── app.html # HTML template
├── public/
│ └── build/ # Compiled assets (generated)
├── vite.config.ts # Vite configuration
├── tsconfig.json # TypeScript configuration
└── package.json # npm dependenciesInstall Dependencies
npm installKey packages included:
| Package | Purpose |
|---|---|
@inertiajs/react | Inertia.js React adapter |
react / react-dom | React 19 |
typescript | Type checking |
vite | Build tool |
tailwindcss | Utility-first CSS |
Vite Configuration
// vite.config.ts
import tailwindcss from '@tailwindcss/vite';
import path from 'path';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, './resources/js'),
},
},
build: {
outDir: 'public/build',
manifest: true,
rollupOptions: {
input: 'resources/js/app.tsx',
},
},
server: {
port: 5173,
strictPort: true,
host: 'localhost',
},
esbuild: {
jsx: 'automatic',
},
});Configuration breakdown:
@alias - Import from@/components/Buttoninstead of../../components/Buttonpublic/build- Compiled assets go here, served by Gomanifest: true- Generates manifest.json for production asset versioning- Port 5173 - Vite dev server runs separately from Go server
TypeScript Configuration
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"baseUrl": ".",
"paths": {
"@/*": ["./resources/js/*"]
}
},
"include": ["resources/js"]
}React Entry Point
// resources/js/app.tsx
import '../css/app.css';
import { createInertiaApp, router } from '@inertiajs/react';
import { createRoot } from 'react-dom/client';
// CSRF token handling
let csrfToken: string | null = null;
router.on('navigate', (event) => {
const pageProps = event.detail.page.props as { csrf_token?: string };
if (pageProps.csrf_token) {
csrfToken = pageProps.csrf_token;
}
});
createInertiaApp({
resolve: async (name) => {
const pages = import.meta.glob('./pages/**/*.tsx', { eager: true });
const page = pages[`./pages/${name}.tsx`];
return page.default;
},
setup({ el, App, props }) {
const root = createRoot(el);
root.render(<App {...props} />);
},
progress: {
color: '#4B5563',
},
});Key parts:
import.meta.glob- Vite’s way to dynamically import all page components- Page resolution -
"Posts/Index"maps to./pages/Posts/Index.tsx - CSRF handling - Token is passed from Go and updated on navigation
- Progress bar - Shows loading indicator during page transitions
Development Workflow
Start Development Servers
Run both servers in separate terminals:
# Terminal 1: Go server
go run main.go
# Terminal 2: Vite dev server
npm run devOr use a process manager like overmind or foreman.
Development vs Production
Development:
- Vite serves assets with hot module replacement
- Changes to React components update instantly
- Go server proxies to Vite for assets
Production:
# Build assets
npm run build
# Run Go server only
./your-appGo serves pre-built assets from public/build/.
HTML Template
<!-- resources/views/app.go.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ .csrfToken }}">
<title>{{ .title | default "Velocity App" }}</title>
{{ .inertiaHead }}
<!-- Development: Vite dev server -->
<script type="module" src="http://localhost:5173/@vite/client"></script>
<script type="module" src="http://localhost:5173/resources/js/app.tsx"></script>
<!-- Production: Compiled assets
<link rel="stylesheet" href="/build/app.css">
<script type="module" src="/build/app.js"></script>
-->
</head>
<body class="font-sans antialiased">
{{ .inertia }}
</body>
</html>The template includes:
{{ .csrfToken }}- CSRF token from Go, updated on each navigation{{ .inertiaHead }}- Inertia head content (title, meta from React){{ .inertia }}- Inertia page data for React hydration
Template Data and Functions
// Share template data
view.ShareTemplateData("app_name", "My Velocity App")
view.ShareTemplateData("version", "1.0.0")
// Share template functions
view.ShareTemplateFunc("formatDate", func(date time.Time) string {
return date.Format("January 2, 2006")
})
view.ShareTemplateFunc("asset", func(path string) string {
return "/build/" + path
})
