
Inertia.js Go Adapter

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
// ...
rootTemplate := "template/app.gohtml" // Path inside the embedded FS, without "./"
inertiaManager := inertia.New(url, rootTemplate, version, templateFS)
2. Register the middleware
mux := http.NewServeMux()
mux.Handle("/", inertiaManager.Middleware(http.HandlerFunc(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")
The default *http.Client uses a 30 second timeout. 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)
Render returns an error if the SSR server fails, it does not fall back to client-side rendering.
For more information, please read the official Server-side Rendering documentation on inertiajs.com.
Page Props
| Name |
Method(s) |
Evaluation |
Full |
Partial |
| Base |
Share, WithProp, Render |
Eager |
✅ |
✅ if requested |
| Optional |
WithOptionalProp |
Lazy |
❌ |
✅ if requested |
| Always |
WithAlwaysProp |
Lazy |
✅ |
✅ always |
| Deferred |
WithDeferredProp |
Lazy |
❌ deferred |
✅ if requested |
| Merge |
WithMergeProp |
Lazy |
✅ |
✅ if requested |
| Deep Merge |
WithDeepMergeProp |
Lazy |
✅ |
✅ if requested |
| Prepend |
WithPrependProp |
Lazy |
✅ |
✅ if requested |
| Scroll |
WithScrollProp |
— |
✅ metadata |
✅ if requested |
| Once |
WithOnceProp, WithOnce |
Lazy |
✅ |
✅ if requested |
| Error |
WithErrorProp |
Eager |
✅ always |
✅ always |
WithRescuedDeferredProp omits the prop and adds its key to rescuedProps on error.
WithScrollProp adds scroll metadata, pair it with a merge or prepend prop.
WithOnce can be combined with Deferred, Merge, Deep Merge, Prepend, and Optional props.
WithOnceProp and WithOnce props are excluded by the X-Inertia-Except-Once-Props header.
X-Inertia-Except-Once-Props header is ignored when the prop is explicitly requested.
X-Inertia-Partial-Data and X-Inertia-Partial-Except match prop keys exactly, no dot notation.
WithErrorProp errors replace a shared errors prop, an inline errors prop replaces both.
Page Settings
| Name |
Method(s) |
Evaluation |
Full |
Partial |
| Flash |
WithFlash |
Eager |
✅ |
✅ |
| Clear History |
WithClearHistory |
Eager |
✅ |
✅ |
| Encrypt History |
WithEncryptHistory |
Eager |
✅ |
✅ |
| Preserve Fragment |
WithPreserveFragment |
Eager |
✅ |
✅ |
WithClearHistory, WithEncryptHistory, and WithPreserveFragment are emitted only when 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)
Rescued deferred prop (context based)
ctx := inertiaManager.WithRescuedDeferredProp(r.Context(), "comments", func() (any, error) {
comments, err := findComments()
if err != nil {
return nil, err
}
return comments, nil
})
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)
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)
Or with multiple nested match on paths:
ctx := inertiaManager.WithDeepMergeProp(r.Context(), "complexData", func() any {
return getComplexData()
}, "users.data.id", "messages.uuid")
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)
A scroll prop does not merge on its own, pair it with a merge or prepend prop:
if r.Header.Get(inertia.HeaderScrollMergeIntent) == "prepend" {
ctx = inertiaManager.WithPrependProp(ctx, "items", getItems)
} else {
ctx = inertiaManager.WithMergeProp(ctx, "items", getItems)
}
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 /
Vue3
https://github.com/petaki/satellite
Homettp
Vite /
Vue3
https://github.com/homettp/homettp
Waterkube
Vite /
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.
评论