# Server Rendering How a page is rendered on the server and picked up by the browser runtime, how to redirect, and how html responses are cached. Pages render on the server and attach in the browser. Pass data into the template through the route handler. The shape is `new html({ ... })`. ```ts export default function route(req: Request, res: Response) { let users = sql(`select * from users`).all(); return new html({ users }); } ``` Usually you'll want to provide all the data needed to render the page from the route. This ensures the page is fully server-rendered. Occasionally you may want to delay data fetching until after the initial page renders; in that case you can call an `@rpc` function from an `oninsert` handler. After first render, `@rpc` and LiveTable are the right tools for browser-initiated data. ## Redirect `redirect(url)` leaves the current page for another url. Use it in place of assigning `window.location` directly. It is one call everywhere: a template handler, a route, middleware, and an `@rpc` all take the same `redirect(url)`. In the browser it drives `window.location`; in a route or middleware it writes a 302; in an `@rpc` the target rides back on the reply and the browser navigates once the call settles. ```ts import { redirect } from "@elements/app"; function attemptSignin(email: string, password: string): string { try { signinUser(email, password); redirect("/"); return ""; } catch (err: any) { return err.message; } } ``` Navigation is asynchronous: the browser fetches the next page while this one stays live, so a reactive change made just before you leave repaints the current page before it unloads. This matters most for auth. `signinUser` above is an `@rpc`, so the compiler awaits it; `session.login` runs on the server and the browser applies the session update, flipping `session.isLoggedIn()`, while the await is still pending, before the `redirect(url)` line runs. So keep session-reactive views off pages you sign in or out from. Render the signin page as the form alone, not a page that swaps between the form and a "you're signed in" panel based on `session.isLoggedIn()`. That panel is the one thing that flashes: it paints where the form was, for the moment before the new page loads. ```html
You're signed in. Go home
{ error = attemptSignin(email, password); if (!error) { redirect("/"); } }}> ...
{ error = attemptSignin(email, password); if (!error) { redirect("/"); } }}> ...
``` Handle an already-signed-in visitor in the route, before the page renders: ```ts export default function route(req: Request, res: Response) { if (session.isLoggedIn()) { redirect("/"); return; } return new html({}); } ``` A session-reactive navbar is fine. It flips to the signed-in state and the destination shows that same state, so the change carries across the navigation rather than flashing. `redirect()` is not control flow. In the browser navigation is asynchronous and the code after it keeps running; on the server the handler keeps running too, and a value returned afterward is discarded rather than written over the redirect. Return early when you want the rest skipped. `redirect()` always sends a 302. For any other status, a route can use the http-specific `res.redirect(url, 301)`. It throws where there is no browser to navigate: a job, a test, module top level, or after a response has already started streaming. ## HTML ETag Caching Every html response carries an etag computed from the page's source, the data passed into the template, and the session state. Unchanged pages return 304 Not Modified, so a browser revisit skips both rendering and transfer when nothing relevant has changed. The page response itself doubles as the cache manifest.