Inertia.js Go Adapter

repository·master·Indexed 43 days ago

https://github.com/petaki/inertia-go

A server-side adapter for Go that enables the creation of single-page apps using the Inertia.js protocol, bridging Go backends with frontend frameworks like Vue or React. It provides tools for rendering Inertia pages, managing global and request-scoped props (including lazy, deferred, and once props), handling Inertia-compatible redirects, and configuring Server-Side Rendering (SSR) via a Node.js server.

Tokens
3.9K
Snippets
17
Records
19
Agent score
55%

What's inside Inertia.js Go Adapter

  1. Manage Page Props and Context

    master

    Inertia props can be shared globally via the inertiaManager or contextually via the request context. Contextual props are added using WithProp methods and then applied to the request using r.WithContext(ctx).

    Prop Types and Evaluation

    MethodEvaluationFullPartial
    Share, WithProp, RenderEager✅✅ if requested
    WithOptionalPropLazy❌✅ if requested
    WithAlwaysPropLazy✅✅ always
    WithDeferredPropLazy❌ deferred✅ if requested
    WithMergePropLazy✅✅ if requested
    WithDeepMergePropLazy✅✅ if requested
    WithPrependPropLazy✅✅ if requested
    WithScrollProp—✅ metadata✅ if requested
    WithOnceProp, WithOnceLazy✅✅ if requested
    WithErrorPropEager✅ always✅ always

    Key Concepts

    • Deferred Props: Use WithDeferredProp for data that should be loaded asynchronously. Use WithRescuedDeferredProp to catch errors and move the key to rescuedProps instead of failing.
    • Merging: Use WithMergeProp or WithDeepMergeProp to combine new data with existing props. You can specify match keys (e.g., id) or nested paths (e.g., users.data.id).
    • Once Props: Use WithOnce or WithOnceProp to ensure data is only sent once. These are excluded by the X-Inertia-Except-Once-Props header.
    • Scroll Props: Use WithScrollProp to handle pagination metadata. It should be paired with a merge or prepend prop.
    // Example: Contextual Deferred Prop
    ctx := inertiaManager.WithDeferredProp(r.Context(), "comments", func() any {
        return getComments()
    })
    r = r.WithContext(ctx)
    
    // Example: Contextual Merge Prop with match key
    ctx := inertiaManager.WithMergeProp(r.Context(), "results", func() any {
        return getResults()
    }, "id")
    r = r.WithContext(ctx)
  2. Initialize an Inertia Manager instance

    master

    To use Inertia, you must first create an inertia.New instance. You can provide a standard file path for the root template or use embed.FS to embed the template into your binary.

    Required parameters:

    • url: The application URL used for redirects.
    • rootTemplate: The path to your root HTML template.
    • version: An optional asset version string.
    • embed.FS (optional): An embedded filesystem containing your templates.
    url := "http://inertia-app.test" // Application URL for redirect
    rootTemplate := "./app.gohtml"   // Root template
    version := ""
    
    // Standard initialization
    inertiaManager := inertia.New(url, rootTemplate, version)
    
    // Initialization with embedded filesystem
    //go:embed template
    var templateFS embed.FS
    inertiaManager := inertia.New(url, rootTemplate, version, templateFS)
  3. Implement the Root Template

    master

    Your root template must provide a way for Inertia to inject the page data.

    Standard Template

    Use the marshal function to inject the .page object into a JSON script tag.

    <body>
        <script data-page="app" type="application/json">{{ marshal .page }}</script>
        <div id="app"></div>
        <script src="js/app.js"></script>
    </body>

    SSR-Enabled Template

    If using Server-Side Rendering, check for the .ssr object to inject the pre-rendered head and body content.

    <head>
        {{ 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>
  4. Register Inertia Middleware

    master

    Register the Inertia middleware with your HTTP router (e.g., http.ServeMux) to handle Inertia-specific requests and responses.

    mux := http.NewServeMux()
    mux.Handle("/", inertiaManager.Middleware(homeHandler))
  5. Configure Server-Side Rendering (SSR)

    master

    To enable SSR, you must provide the URL of your Node.js SSR server. Note that Render will return an error if the SSR server fails; it does not automatically fall back to client-side rendering.

    Options:

    • EnableSsrWithDefault(): Uses the default URL http://127.0.0.1:13714/render.
    • EnableSsr(url): Uses a custom URL (e.g., for production or Vite dev server).
    • Custom *http.Client: You can provide a client with a custom timeout (the default is 30 seconds).
    // Default SSR
    inertiaManager.EnableSsrWithDefault()
    
    // Custom SSR URL
    inertiaManager.EnableSsr("http://ssr-host:13714/render")
    
    // Vite dev server SSR
    inertiaManager.EnableSsr("http://localhost:5173/__inertia_ssr")
    
    // Custom client with timeout
    client := &http.Client{
        Timeout: 10 * time.Second,
    }
    inertiaManager.EnableSsr("http://ssr-host:13714/render", client)
  6. Configure Page Settings

    master

    You can modify page settings like flash messages and history behavior using context-based methods. These methods return a new context that must be applied to the request.

    MethodDescription
    WithFlashAdds flash data (e.g., success messages)
    WithClearHistoryClears the browser history
    WithEncryptHistoryEncrypts the browser history
    WithPreserveFragmentPreserves the URL fragment
    // Example: Adding flash messages
    ctx := inertiaManager.WithFlash(r.Context(), map[string]any{
        "success": "Item created successfully",
    })
    r = r.WithContext(ctx)
  7. Render Inertia pages in handlers

    master

    Use the Render method within your HTTP handlers to serve an Inertia page. You must specify the component name (e.g., home/Index) and a map of props.

    func homeHandler(w http.ResponseWriter, r *http.Request) {
        // Render with props
        err := inertiaManager.Render(w, r, "home/Index", map[string]any{
            "total": 32,
        })
        if err != nil {
            // Handle server error...
        }
    }
  8. Manage request-scoped props via Context

    master

    Instead of passing all props directly into Render, you can attach them to the request context.Context. This is useful for middleware or deep call stacks. The Render method will automatically collect these from the context.

    Common Prop Methods:

    • WithProp(ctx, key, value): Standard prop.
    • WithOptionalProp(ctx, key, func() any): A prop that is only included if the function returns a non-nil value.
    • WithAlwaysProp(ctx, key, func() any): A prop that is always included.
    • WithDeferredProp(ctx, key, func() (any, error), group...): A prop that is resolved lazily. You can assign it to a group (defaults to "default").
    • WithMergeProp(ctx, key, func() any, matchOn...): Merges the value into an existing prop.
    • WithDeepMergeProp(ctx, key, func() any, matchOn...): Performs a deep merge into an existing prop.
    • WithPrependProp(ctx, key, func() any, matchOn...): Prepends the value to an existing array prop.
    • WithOnceProp(ctx, key, prop...): A prop that is only sent once (e.g., on the initial page load).
    • WithErrorProp(ctx, key, value): Attaches error data to the errors object in the Inertia response.
    // Example: Attaching a prop via context
    ctx = i.WithProp(r.Context(), "user", user)
    
    // Example: Deferred prop for heavy computation
    ctx = i.WithDeferredProp(r.Context(), "stats", func() any { return fetchStats() }, "heavy-group")
    
    // Use the modified context in your handler
    err := i.Render(w, r.WithContext(ctx), "Dashboard", nil)
    ctx = i.WithProp(r.Context(), "user", user)
    err := i.Render(w, r.WithContext(ctx), "Dashboard", nil)
  9. Perform Inertia-compatible redirects

    master

    The Location method handles redirects differently depending on whether the request is an Inertia request or a standard browser request:

    • Inertia Request: Sets the HeaderLocation and returns a 409 Conflict status. This tells the Inertia frontend to perform a full page reload to the new URL.
    • Standard Request: Performs a standard 302 Found redirect.
    // Inside a handler
    if userNotAuthorized { 
        i.Location(w, r, "/login")
        return
    }
    i.Location(w, r, "/login")
  10. Manage Flash, History, and Navigation via Context

    master

    You can control Inertia's navigation behavior and flash messages by modifying the request context:

    • WithFlash(ctx, data map[string]any): Sets flash messages to be sent in the next response.
    • WithClearHistory(ctx): Tells Inertia to clear the browser history.
    • WithEncryptHistory(ctx): Tells Inertia to encrypt the history state.
    • WithPreserveFragment(ctx): Tells Inertia to preserve the URL fragment (hash).
    • WithScrollProp(ctx, key, prop): Sets scroll position properties.
    // Example: Redirecting with flash and clearing history
    ctx = i.WithFlash(r.Context(), map[string]any{"success": "Profile updated!"})
    ctx = i.WithClearHistory(ctx)
    
    // Use the modified context in your handler
    err := i.Render(w, r.WithContext(ctx), "Dashboard", nil)
    ctx = i.WithFlash(r.Context(), map[string]any{"success": "Profile updated!"})
    ctx = i.WithClearHistory(ctx)
    
    err := i.Render(w, r.WithContext(ctx), "Dashboard", nil)
  11. Share props and data globally

    master

    You can share data that will be included in every Inertia response using the following methods:

    • Share(key string, value any): Adds a property to the sharedProps map. These are sent to the frontend in every request.
    • ShareViewData(key string, value any): Adds data that is available to your Go HTML templates (the root template) but is not necessarily part of the Inertia JSON props.
    • ShareFunc(key string, value any): Registers a function in the Go template's FuncMap, making it available within your root template.
    i.Share("auth", authUser)
    
    i.ShareViewData("appName", "My Awesome App")
    
    i.ShareFunc("formatDate", myDateFormatterFunc)