DEV Community

Eddy Javier Jorge Herrera
Eddy Javier Jorge Herrera

Posted on

Goravel + Inertia.js: Build Laravel-style SPAs in Go — no API layer required

Goravel Inertia Logotype

Inertia.js v3 is now an official Goravel package. Server-driven SPAs with Vue 3 or React, Vite HMR, SSR with CSR fallback — the Laravel DX, in Go.

If you come from Laravel, you know the feeling: Inertia.js lets you build a real single-page app — Vue or React on the frontend — without building an API. No REST endpoints, no client-side routing, no token juggling. Your controllers return pages, props flow in, and it just works.

Go never had that experience for a full-stack framework. Now it does.

goravel/inertia is the official Inertia.js v3 adapter for Goravel, the Go framework with Laravel's architecture and conventions. It started as a community package and has just been adopted into the Goravel organization.

This is what a controller looks like:

func (c *HomeController) Index(ctx http.Context) http.Response {
    return facades.Inertia().Render(ctx, "Home", map[string]any{
        "message": "Hello from Goravel + Inertia",
    })
}
Enter fullscreen mode Exit fullscreen mode

That's it. Home is a Vue or React component. message arrives as a prop. No serializers, no fetch calls, no route duplication.

From zero to a running SPA in four commands

go run . artisan package:install github.com/goravel/inertia
go run . artisan inertia:install              # Vue 3 (default)
# go run . artisan inertia:install --stack=react   # or React 19
npm install
npm run dev   # then, in another shell: go run .
Enter fullscreen mode Exit fullscreen mode

The inertia:install command scaffolds everything: config/inertia.go, the root template, a full demo app (Home / Feed / Contact / About) with its Go controllers, the HandleInertiaRequests middleware, vite.config.ts and package.json. It even wires routes/web.go for you.

Open http://localhost:3000 and you have a working SPA with hot-module reload.

The parts that usually hurt — already solved

Inertia v3 props: deferred, optional, merge, infinite scroll

The full v3 prop toolbox is available from your controllers:

func (c *DashboardController) Index(ctx http.Context) http.Response {
    // Loaded after first paint — the page renders instantly,
    // stats stream in behind it (<Deferred> on the client).
    facades.Inertia().Defer(ctx, "stats", func() any { return loadStats() })

    // Client-side "load more" pagination: new items merge into the list.
    facades.Inertia().Merge(ctx, "posts", func() any { return nextPage(ctx) })

    return facades.Inertia().Render(ctx, "Dashboard", nil)
}
Enter fullscreen mode Exit fullscreen mode

Defer, Optional, Always, Merge, DeepMerge, Prepend, Once, Scroll — same semantics as the Laravel adapter.

Shared props, the Laravel way

inertia:install scaffolds app/http/middleware/handle_inertia_requests.go — the Go analogue of Laravel's HandleInertiaRequests::share():

func share(ctx http.Context) map[string]any {
    return map[string]any{
        "appName": facades.Config().GetString("app.name"),
        "auth": map[string]any{
            "user": authUser(ctx),
        },
    }
}
Enter fullscreen mode Exit fullscreen mode

Runs once per request, context-aware, and you own the file.

Flash messages & validation errors — bridged automatically

Fail a validation, flash the errors, redirect back. The package mirrors them into props.errors and props.flash on the next request — exactly where useForm() expects them:

func (c *ContactController) Store(ctx http.Context) http.Response {
    validator, err := ctx.Request().Validate(map[string]any{
        "email": "required|email",
    })
    if err != nil || validator.Fails() {
        facades.Inertia().FlashErrors(ctx, validator.Errors())
        return facades.Inertia().Redirect(ctx, "/contact")
    }

    ctx.Request().Session().Flash("success", "Saved!")
    return facades.Inertia().Redirect(ctx, "/contact")
}
Enter fullscreen mode Exit fullscreen mode

Redirect is Inertia-aware too: it answers 303 on PUT/PATCH/DELETE so the browser re-fetches with GET, mirroring inertia-laravel.

Vite: dev and prod, no configuration

  • npm run dev writes public/hot → assets load from the dev server with HMR (React Fast Refresh preamble included for the React stack).
  • npm run build emits hashed assets + manifest → the {{ vite "resources/js/app.ts" }} template helper switches automatically.
  • The Inertia asset version is derived from the manifest hash, so every deploy busts stale clients — no manual version bumps.

SSR that degrades gracefully

npm run build:ssr && npm run ssr    # SSR server on :13714
INERTIA_SSR=true go run .
Enter fullscreen mode Exit fullscreen mode

Here's my favorite detail: if the SSR server is down or unreachable, the adapter falls back to client-side rendering instead of serving a blank page. A warning lands in the logs; your users never notice.

Design notes, for the curious

  • Built on petaki/inertia-go (MIT) for the wire protocol, wrapped in Goravel idioms: a contracts.Inertia interface, a service provider, a facade, generated mocks.
  • Your app depends only on the contract — the engine underneath can evolve without breaking you.
  • Session, validation, routing and middleware integrate with Goravel v1.18's native modules.
  • Test suite runs with -race, CI on every push, MIT licensed.

Requirements

Go 1.26+
Goravel v1.18+
Node 18+ (Vite frontend)

Try it, break it, tell us

The package just moved into the Goravel organization and this is its first official release — feedback is gold right now:

If you've been waiting for a reason to try Go for full-stack web work without giving up the Laravel developer experience — this is it.

Top comments (0)