This chapter follows Islands and client directives in Astro.
Static pages are fast and cheap to host, but they are the same for every visitor and only change when you rebuild. A login page, a search results page, or a form that writes to a database needs code that runs per request. Astro lets you add that route by route instead of turning the whole site into a server application.
Prerendered and on-demand routes
Astro has two output modes, and both allow mixing:
| Config | Default for each route | Opt a route out |
|---|---|---|
output: 'static' (default) |
Prerendered at build time | export const prerender = false |
output: 'server' |
Rendered on demand | export const prerender = true |
Pick the mode that matches most of your routes. A content site keeps static and marks the few dynamic pages; an application uses server and marks the marketing pages as prerendered.
---export const prerender = false;
const user = Astro.cookies.get('user')?.value;if (!user) return Astro.redirect('/login/');---<h1>Welcome back, {user}</h1>An on-demand dynamic route such as src/pages/products/[id].astro does not need getStaticPaths; Astro.params.id comes from the request URL and the page can look the product up at request time.
Adapters
On-demand routes need a runtime to answer requests. An adapter builds the server part of your site for a specific host:
npx astro add nodenpx astro add cloudflarenpx astro add vercelnpx astro add netlify@astrojs/node produces a standalone Node.js server or middleware for an existing one. The hosting adapters produce functions in the format each platform expects. Prerendered routes are still emitted as static files and served without invoking the server. This knowledgebase has no adapter: every page is prerendered, so the build output is plain files.
Request data, cookies, and responses
Inside an on-demand page or endpoint, the Astro global exposes the request:
| API | Gives you |
|---|---|
Astro.request |
The standard Request: method, headers, body |
Astro.url |
The parsed URL, including searchParams |
Astro.params |
Values from dynamic route segments |
Astro.cookies |
get, set, has, and delete with cookie options |
Astro.redirect(path, status) |
A redirect response |
Astro.response |
Status and headers for the page response |
Astro.locals |
Data placed there by middleware |
---export const prerender = false;
const query = Astro.url.searchParams.get('q') ?? '';const results = query ? await searchArticles(query) : [];if (query && results.length === 0) Astro.response.status = 404;---<form><input name="q" value={query} /></form><p>{results.length} results</p>In a prerendered page these values reflect the build, not a visitor: there are no request headers or cookies at build time.
Middleware
src/middleware.ts runs before every route is rendered. It can read and modify the request, short-circuit with a response, and pass data to pages through context.locals:
import { defineMiddleware, sequence } from 'astro:middleware';
const auth = defineMiddleware(async (context, next) => { const token = context.cookies.get('session')?.value; context.locals.user = token ? await lookupUser(token) : null; if (context.url.pathname.startsWith('/admin/') && !context.locals.user) { return context.redirect('/login/'); } return next();});
const timing = defineMiddleware(async (_context, next) => { const started = performance.now(); const response = await next(); response.headers.set('Server-Timing', `render;dur=${performance.now() - started}`); return response;});
export const onRequest = sequence(auth, timing);Type locals once in src/env.d.ts by extending App.Locals, and every page reading Astro.locals.user is checked. Middleware also runs for prerendered pages during the build, which is useful for shared setup but means request-specific logic only matters for on-demand routes.
Actions
Actions are type-safe server functions with input validation, callable from forms and from client code. Define them in src/actions/index.ts:
import { defineAction } from 'astro:actions';import { z } from 'astro/zod';
export const server = { subscribe: defineAction({ accept: 'form', input: z.object({ email: z.string().email(), }), handler: async ({ email }) => { await addSubscriber(email); return { email }; }, }),};An HTML form can post to the action directly, which works without any client JavaScript:
---import { actions } from 'astro:actions';export const prerender = false;
const result = Astro.getActionResult(actions.subscribe);---{result?.data && <p>Subscribed {result.data.email}</p>}{result?.error && <p>Please enter a valid email address.</p>}<form method="POST" action={actions.subscribe}> <input type="email" name="email" required /> <button>Subscribe</button></form>From an island, call await actions.subscribe(formData) and receive { data, error }; isInputError(error) tells validation failures apart from other errors. Actions replace most hand-written API endpoints, because validation and types are shared between caller and server.
Environment variables
import.meta.env works as in Vite: variables prefixed PUBLIC_ are exposed to client code, everything else stays on the server. For validated, typed variables, declare a schema with envField:
import { defineConfig, envField } from 'astro/config';
export default defineConfig({ env: { schema: { API_URL: envField.string({ context: 'client', access: 'public' }), API_SECRET: envField.string({ context: 'server', access: 'secret' }), }, },});Read them from astro:env/client or astro:env/server. Astro fails the build when a required variable is missing, and refuses to import a secret into client code.
Choosing static or on demand
Prerender everything that is the same for all visitors, even pages that change daily; a scheduled rebuild is cheaper and faster than rendering per request. Use on-demand rendering when the response depends on the visitor or the request: authentication, personalized data, search parameters, and form submissions. For a mostly static page with one personal fragment, reach for a server island before making the whole page dynamic.
This is the last chapter in the Astro series. For a compact reference, see the Astro cheat sheet.
Check your understanding
Question 1 of 7
0 of 7 checked