Inertia.js Go Adapter

April 27, 2026 · View on GitHub

Inertia GO

Inertia.js Go Adapter

Build Status License: MIT

An Inertia.js server-side adapter for Go. Visit inertiajs.com to learn more.

Installation

Install the package using the go get command:

go get github.com/petaki/inertia-go

Usage

1. Create new instance

url := "http://inertia-app.test" // Application URL for redirect
rootTemplate := "./app.gohtml"   // Root template, see the example below
version := ""                    // Asset version

inertiaManager := inertia.New(url, rootTemplate, version)

Or create with embed.FS for root template:

import "embed"

//go:embed template
var templateFS embed.FS

// ...

inertiaManager := inertia.New(url, rootTemplate, version, templateFS)

2. Register the middleware

mux := http.NewServeMux()
mux.Handle("/", inertiaManager.Middleware(homeHandler))

3. Render in handlers

func homeHandler(w http.ResponseWriter, r *http.Request) {
    // ...

    err := inertiaManager.Render(w, r, "home/Index", nil)
    if err != nil {
        // Handle server error...
    }
}

Or render with props:

// ...

err := inertiaManager.Render(w, r, "home/Index", map[string]any{
    "total": 32,
})

//...

4. Server-side Rendering (Optional)

First, enable SSR with the url of the Node server:

inertiaManager.EnableSsrWithDefault() // http://127.0.0.1:13714/render

Or with custom url:

inertiaManager.EnableSsr("http://ssr-host:13714/render")

Or with the Vite dev server:

inertiaManager.EnableSsr("http://localhost:5173/__inertia_ssr")

You can also provide a custom *http.Client:

client := &http.Client{
    Timeout: 10 * time.Second,
}

inertiaManager.EnableSsr("http://ssr-host:13714/render", client)
inertiaManager.EnableSsrWithDefault(client)

For more information, please read the official Server-side Rendering documentation on inertiajs.com.

Page Props

NameMethod(s)EvaluationFullPartial
BaseShare, WithProp, RenderEager✅ if requested
OptionalWithOptionalPropLazy✅ if requested
AlwaysWithAlwaysPropLazy✅ always
DeferredWithDeferredPropLazy❌ deferred✅ if requested
MergeWithMergePropLazy✅ if requested
Deep MergeWithDeepMergePropLazy✅ if requested
PrependWithPrependPropLazy✅ if requested
ScrollWithScrollProp✅ metadata✅ metadata
OnceWithOnceProp, WithOnceLazy✅ if requested
ErrorWithErrorPropEager✅ always✅ always
  • WithOnce can be combined with Deferred, Merge, Deep Merge, Prepend, and Optional props.
  • WithOnceProp and WithOnce props are excluded when listed in the X-Inertia-Except-Once-Props header.
  • WithScrollProp adds scroll metadata to the page response for infinite scroll support.
  • WithErrorProp errors are merged with any inline errors map passed to Render.

Page Settings

NameMethod(s)EvaluationFullPartial
FlashWithFlashEager
Clear HistoryWithClearHistoryEager
Encrypt HistoryWithEncryptHistoryEager
Preserve FragmentWithPreserveFragmentEager
  • WithClearHistory, WithEncryptHistory, and WithPreserveFragment are emitted only when set to true.

Examples

The following examples show how to use the package.

Share a function with root template (globally)

inertiaManager.ShareFunc("asset", assetFunc)
<script src="{{ asset "js/app.js" }}"></script>

Share data with root template (globally)

inertiaManager.ShareViewData("env", "production")
{{ if eq .env "production" }}
    ...
{{ end }}

Share data with root template (context based)

ctx := inertiaManager.WithViewData(r.Context(), "meta", meta)
r = r.WithContext(ctx)
<meta name="description" content="{{ .meta }}">

Share a prop (globally)

inertiaManager.Share("title", "Inertia App Title")

Share a prop (context based)

func authenticate(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // ...
        
        ctx := inertiaManager.WithProp(r.Context(), "authUserID", user.ID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

Optional prop (context based)

ctx := inertiaManager.WithOptionalProp(r.Context(), "extra", func() any {
    return getExtra()
})
r = r.WithContext(ctx)

Always prop (context based)

ctx := inertiaManager.WithAlwaysProp(r.Context(), "notifications", func() any {
    return getNotifications()
})
r = r.WithContext(ctx)

Deferred prop (context based)

ctx := inertiaManager.WithDeferredProp(r.Context(), "comments", func() any {
    return getComments()
})
r = r.WithContext(ctx)

Deferred prop with group (context based)

ctx := inertiaManager.WithDeferredProp(r.Context(), "comments", func() any {
    return getComments()
}, "my-group")
r = r.WithContext(ctx)

Merge prop (context based)

ctx := inertiaManager.WithMergeProp(r.Context(), "results", func() any {
    return getResults()
})
r = r.WithContext(ctx)

Or with match on:

ctx := inertiaManager.WithMergeProp(r.Context(), "results", func() any {
    return getResults()
}, "id")
r = r.WithContext(ctx)

Or with multiple nested match on paths:

ctx := inertiaManager.WithMergeProp(r.Context(), "complexData", func() any {
    return getComplexData()
}, "users.data.id", "messages.uuid")
r = r.WithContext(ctx)

Deep merge prop (context based)

ctx := inertiaManager.WithDeepMergeProp(r.Context(), "settings", func() any {
    return getSettings()
})
r = r.WithContext(ctx)

Or with match on:

ctx := inertiaManager.WithDeepMergeProp(r.Context(), "settings", func() any {
    return getSettings()
}, "id")
r = r.WithContext(ctx)

Prepend prop (context based)

ctx := inertiaManager.WithPrependProp(r.Context(), "notifications", func() any {
    return getNotifications()
})
r = r.WithContext(ctx)

Or with match on:

ctx := inertiaManager.WithPrependProp(r.Context(), "notifications", func() any {
    return getNotifications()
}, "id")
r = r.WithContext(ctx)

Scroll prop (context based)

ctx := inertiaManager.WithScrollProp(r.Context(), "items", inertia.ScrollPageProp{
    PageName:    "page",
    CurrentPage: 1,
    NextPage:    2,
})
r = r.WithContext(ctx)

Once prop (context based)

ctx := inertiaManager.WithOnceProp(r.Context(), "plans", func() any {
    return getPlans()
})
r = r.WithContext(ctx)

Once modifier (context based)

ctx := inertiaManager.WithMergeProp(r.Context(), "activity", func() any {
    return getActivity()
})
ctx = inertiaManager.WithOnce(ctx, "activity")
r = r.WithContext(ctx)

Or with expiration:

expiresAt := time.Now().Add(24 * time.Hour).UnixMilli()
ctx := inertiaManager.WithDeferredProp(r.Context(), "permissions", func() any {
    return getPermissions()
})
ctx = inertiaManager.WithOnce(ctx, "permissions", inertia.OncePageProp{ExpiresAt: &expiresAt})
r = r.WithContext(ctx)

Error prop (context based)

ctx := inertiaManager.WithErrorProp(r.Context(), "email", "Invalid email")
r = r.WithContext(ctx)

Or with multiple fields:

ctx := inertiaManager.WithErrorProp(r.Context(), "email", "Invalid email")
ctx = inertiaManager.WithErrorProp(ctx, "password", "Too short")
r = r.WithContext(ctx)

Flash (context based)

ctx := inertiaManager.WithFlash(r.Context(), map[string]any{
    "success": "Item created successfully",
})
r = r.WithContext(ctx)

Clear history (context based)

ctx := inertiaManager.WithClearHistory(r.Context())
r = r.WithContext(ctx)

Encrypt history (context based)

ctx := inertiaManager.WithEncryptHistory(r.Context())
r = r.WithContext(ctx)

Preserve fragment (context based)

ctx := inertiaManager.WithPreserveFragment(r.Context())
r = r.WithContext(ctx)

Root template

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="css/app.css" rel="stylesheet">
        <link rel="icon" type="image/x-icon" href="favicon.ico">
    </head>
    <body>
        <script data-page="app" type="application/json">{{ marshal .page }}</script>
        <div id="app"></div>
        <script src="js/app.js"></script>
    </body>
</html>

Root template with Server-side Rendering (SSR)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="css/app.css" rel="stylesheet">
        <link rel="icon" type="image/x-icon" href="favicon.ico">
        {{ if .ssr }}
            {{ raw .ssr.Head }}
        {{ end }}
    </head>
    <body>
        {{ if not .ssr }}
            <script data-page="app" type="application/json">{{ marshal .page }}</script>
            <div id="app"></div>
        {{ else }}
            {{ raw .ssr.Body }}
        {{ end }}
        <script src="js/app.js"></script>
    </body>
</html>

Vite Integration

For Vite integration, check out the Usage with Inertia section in the petaki/support-go package.

Example Apps

Satellite

Vite Vite / Vue3 Vue3

https://github.com/petaki/satellite

Homettp

Vite Vite / Vue3 Vue3

https://github.com/homettp/homettp

Waterkube

Vite Vite / Vue3 Vue3

https://github.com/waterkube/waterkube

Contributors

Reporting Issues

If you are facing a problem with this package or found any bug, please open an issue on GitHub.

License

The MIT License (MIT). Please see License File for more information.