> ## 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.

# HTML Views

Copper renders server-side HTML using Go templates. Layouts, pages, and partials are all embedded into your binary.

<Note>
  New projects default to a React frontend with Inertia; see [Frontend](/frontend/overview). This page covers classic server-rendered Go templates, which are a great fit for simpler apps and HTMX-style interactivity.
</Note>

### Template Directory

Templates live under `web/src` in three conventional directories:

```
web/src/
├── layouts/
│   └── main.html        # page shell: <html>, <head>, nav
├── pages/
│   ├── index.html
│   └── rocket.html
└── partials/
    └── rocket-card.html # reusable fragments
```

A layout defines the shell and renders the page inside it:

```html web/src/layouts/main.html theme={null}
<!DOCTYPE html>
<html>
<head>
    <title>Mission Control</title>
</head>
<body>
    {{ template "content" . }}
</body>
</html>
```

```html web/src/pages/rocket.html theme={null}
{{ define "content" }}
<h1>{{ .Rocket.Name }}</h1>
<p>Status: {{ .Rocket.Status }}</p>
{{ end }}
```

### Rendering Pages

Copper provides `chttp.HTMLReaderWriter` for writing HTML responses. Inject it into your router and call `WriteHTML`:

```go theme={null}
type NewRouterParams struct {
	Rockets *Svc
	HTML    *chttp.HTMLReaderWriter
	Logger  clogger.Logger
}
```

```go theme={null}
func (ro *Router) HandleRocketPage(w http.ResponseWriter, r *http.Request) {
	rocket, err := ro.rockets.Get(r.Context(), chttp.URLParams(r)["id"])
	if err != nil {
		ro.html.WriteHTMLError(w, r, err)
		return
	}

	ro.html.WriteHTML(w, r, chttp.WriteHTMLParams{
		PageTemplate: "rocket.html",
		Data:         map[string]any{"Rocket": rocket},
	})
}
```

`LayoutTemplate` defaults to `main.html` and `StatusCode` to `200`; you may set either explicitly when needed. On errors, an appropriate page will automatically be chosen for you: `internal-error.html` for 500s and `not-found.html` for 404s.

### Template Functions

Every template has the full [Sprig](https://masterminds.github.io/sprig/) function library available, including `upper`, `date`, and `mustToRawJson`.

You may add your own functions using `chttp.HTMLRenderFunc`. Since render funcs receive the current request, they can expose per-request state such as the signed-in user:

```go web/wire.go theme={null}
func HTMLRenderFuncs() []chttp.HTMLRenderFunc {
	return []chttp.HTMLRenderFunc{
		{
			Name: "currentUser",
			Func: func(r *http.Request) (any, error) {
				return func() *sessions.User {
					return sessions.UserFromCtx(r.Context())
				}, nil
			},
		},
	}
}
```

```html theme={null}
<p>Welcome back, {{ (currentUser).Name }}</p>
```

### Partials

Partials in `web/src/partials` are reusable fragments. Render one inside any template with the built-in `partial` function:

```html theme={null}
{{ range .Rockets }}
    {{ partial "rocket-card" . }}
{{ end }}
```

You may also write a partial directly as a response, which works well for [HTMX](https://htmx.org)-style updates that swap a fragment into the page:

```go theme={null}
func (ro *Router) HandleRocketStatus(w http.ResponseWriter, r *http.Request) {
	rocket, err := ro.rockets.Get(r.Context(), chttp.URLParams(r)["id"])
	if err != nil {
		ro.html.WriteHTMLError(w, r, err)
		return
	}

	ro.html.WritePartial(w, r, chttp.WritePartialParams{
		Name: "rocket-card",
		Data: rocket,
	})
}
```

### Static Assets

Files in `web/public` are served under `/static/`. For example, `web/public/logo.svg` is available at `/static/logo.svg`. In production these files are embedded into your binary, while in development they are read from disk.

### Local Development

Two config flags make template development pleasant, and both are enabled in `config/dev.toml` for new projects:

```toml theme={null}
[chttp]
use_local_html = true     # read templates & static files live from ./web — no rebuild
render_html_error = true  # show the actual error in the browser instead of a blank 500
```

### Single-Page Apps

If your frontend handles its own routing, you may configure every unmatched GET request to render `index.html`:

```toml theme={null}
[chttp]
enable_single_page_routing = true
```

### API-Only Apps

If your application doesn't serve HTML at all, use `chttp.WireModuleEmptyHTML` in place of the HTML and static dir bindings; it satisfies wire with empty stand-ins. Projects created with `-frontend=none` are configured this way.
