> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gocopper.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Inertia

[Inertia](https://inertiajs.com) lets you build a modern React frontend with classic server-side routing. Your Go routers decide which page to show and what data it receives, while React renders it. Since the server drives navigation, there is no client-side router to maintain and no REST layer between your pages and your services. This is the default stack for new Copper projects.

The `inertia` package (from [gocopper/pkg](https://github.com/gocopper/pkg)) provides the server-side adapter, wired in automatically by `copper create`.

### Rendering a Page

Inject `*inertia.Renderer` into your router and call `Render` with a component name and props:

```go pkg/rockets/router.go theme={null}
func (ro *Router) HandleRocketsPage(w http.ResponseWriter, r *http.Request) {
	rockets, err := ro.rockets.List(r.Context())
	if err != nil {
		ro.logger.Error("Failed to list rockets", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	ro.inertia.Render(w, r, inertia.RenderParams{
		Component: "rockets",
		Props: map[string]any{
			"rockets": rockets,
		},
	})
}
```

Components live in `web/src/pages/` and receive props directly:

```tsx web/src/pages/rockets.tsx theme={null}
export default function Rockets({ rockets }) {
  return (
    <ul>
      {rockets.map((rocket) => (
        <li key={rocket.id}>{rocket.name}</li>
      ))}
    </ul>
  )
}
```

On a full page load, Copper renders the HTML shell with the page data embedded. On navigation, Inertia requests only the JSON; the renderer will detect which is which automatically.

### Forms

You may read and validate a form submission using `ReadForm`, which decodes the JSON body and validates its `valid:` tags ([govalidator](https://github.com/asaskevich/govalidator)). On validation failure, the error is flashed automatically and `ReadForm` returns `false`, so you simply redirect back:

```go theme={null}
func (ro *Router) HandleCreateRocket(w http.ResponseWriter, r *http.Request) {
	var form struct {
		Name string `json:"name" valid:"required"`
	}

	if !ro.inertia.ReadForm(w, r, &form) {
		ro.inertia.Redirect303(w, r, "/rockets")
		return
	}

	rocket, err := ro.rockets.Create(r.Context(), form.Name)
	if err != nil {
		ro.inertia.FlashProps(r, map[string]any{
			"error": "Could not create the rocket. Please try again.",
		})
		ro.inertia.Redirect303(w, r, "/rockets")
		return
	}

	ro.inertia.Redirect303(w, r, "/rockets/"+rocket.ID)
}
```

This is the classic post/redirect/get pattern: mutations always end in `Redirect303`, and the next render picks up any flashed props. Validation errors appear on the client under `flash.validationError`.

<Warning>
  `ReadForm`'s two failure modes differ: a body that fails validation flashes the error and leaves the response for you to write, but a body that can't be decoded as JSON at all writes an error response itself. Both return `false`.
</Warning>

### Flash Props

`FlashProps` stores props for a single upcoming render, which is perfect for success and error messages across a redirect:

```go theme={null}
ro.inertia.FlashProps(r, map[string]any{
	"success": "Rocket launched!",
})
ro.inertia.Redirect303(w, r, "/rockets")
```

The next page render receives them under the `flash` prop, after which they are gone:

```tsx theme={null}
export default function Rockets({ rockets, flash }) {
  return (
    <>
      {flash?.success && <Banner>{flash.success}</Banner>}
      {/* ... */}
    </>
  )
}
```

### Shared Props

`ShareProps` adds props to the current request's render from anywhere. It is typically called from a middleware so that every page in a section receives common data:

```go theme={null}
func (mw *PropsMiddleware) Handle(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		mw.inertia.ShareProps(r.Context(), map[string]any{
			"missionName": mw.config.MissionName,
		})

		next.ServeHTTP(w, r)
	})
}
```

You may attach the middleware per-route via `Middlewares`, or globally in `pkg/app/handler.go`.

<Note>
  `ShareProps` identifies the request via its request ID, so `chttp.SetRequestIDInCtxMiddleware()` must be in your global middlewares. Scaffolded projects include it by default. Successive calls for the same request replace previously shared props rather than merging them, so you should share everything in a single call.
</Note>

### Partial Reloads

When the client reloads only specific props (`router.reload({ only: ["rockets"] })`), you may skip computing everything else with `ShouldLoadProp`:

```go theme={null}
props := map[string]any{}

if inertia.ShouldLoadProp(r, "telemetry") {
	props["telemetry"] = ro.rockets.LoadTelemetry(ctx)
}
```

`ShouldLoadProp` returns `true` on full renders and only for the requested props on partial reloads.

### Deferred Props

Deferred props let a page render immediately while slow data loads in a follow-up request. Declare them in `DeferredProps`, and guard the expensive work with `ShouldLoadDeferredProp`:

```go theme={null}
func (ro *Router) HandleMissionControl(w http.ResponseWriter, r *http.Request) {
	props := map[string]any{
		"mission": ro.rockets.Mission(r.Context()),
	}

	if inertia.ShouldLoadDeferredProp(r, "telemetry") {
		props["telemetry"] = ro.rockets.LoadTelemetry(r.Context())
	}

	ro.inertia.Render(w, r, inertia.RenderParams{
		Component: "mission-control",
		Props:     props,
		DeferredProps: map[string][]string{
			"default": {"telemetry"},
		},
	})
}
```

Unlike `ShouldLoadProp`, `ShouldLoadDeferredProp` returns `false` on the initial render: the prop is stripped from the first response and fetched immediately afterwards. On the client, wrap the slow section in Inertia's `<Deferred>`:

```tsx theme={null}
<Deferred data="telemetry" fallback={<Spinner />}>
  <TelemetryPanel />
</Deferred>
```

### Customizing the Renderer

The renderer is immutable; each `With*` method returns a copy, so you may configure it once in your constructor:

```go theme={null}
func NewRouter(p NewRouterParams) *Router {
	return &Router{
		inertia: p.Inertia.WithBasePath("/admin"),
	}
}
```

* `WithBasePath(path)` mounts a section under a URL prefix. Redirects and page URLs have the prefix stripped so that client-side paths stay clean; it pairs with [`chttp.base_path`](/http/routing#serving-under-a-base-path).
* `WithLayoutTemplate(name)` renders pages inside a different layout than `main.html`.
* `WithComponent(name)` sets a default component for all renders.

`RenderParams` accepts per-render `LayoutTemplate` and `BasePath` overrides too.

### Server-Side Rendering

SSR is opt-in. Run the Inertia SSR server (see the [Inertia SSR guide](https://inertiajs.com/server-side-rendering)) and enable it in config:

```toml theme={null}
[inertia]
ssr = true
ssr_server = "http://localhost:13714"
```

Copper sends each page to the SSR server and injects the rendered HTML. If the SSR server is down or errors, Copper logs a warning and falls back to client-side rendering, so SSR will never take your pages down.

### Configuration

| Key          | Default                  | Description                                |
| ------------ | ------------------------ | ------------------------------------------ |
| `ssr`        | `false`                  | Render pages through an Inertia SSR server |
| `ssr_server` | `http://localhost:13714` | Address of the SSR server                  |
