This Astro SSR cheatsheet collects every server feature in Astro 7 on one page: how to turn on-demand rendering on, and the APIs for server islands, actions, middleware, sessions, cookies, endpoints and response headers. Each section has the minimum code to use the feature. For when and why, read the Astro SSR guide.
Key Takeaways
- Every server feature needs an adapter.
- Default
output: 'static'plusexport const prerender = falseper page is the usual setup.- Server islands keep a page static while one component renders per request.
- Middleware and
localsrun only on on-demand requests (at build time for static pages).
Turn it on
npx astro add node # or cloudflare, netlify, vercel
// astro.config.mjs
import node from '@astrojs/node';
export default defineConfig({ adapter: node({ mode: 'standalone' }) });
| Setup | Config | Per page |
|---|---|---|
| Mostly static | output: 'static' (default) |
export const prerender = false |
| Mostly dynamic | output: 'server' |
export const prerender = true |
prerender must be a literal; see Invalid prerender export.
On-demand page
---
export const prerender = false;
const { slug } = Astro.params;
const data = await fetch(`https://api.example.com/items/${slug}`).then((r) => r.json());
if (!data) return new Response(null, { status: 404 });
Astro.response.headers.set('Cache-Control', 'public, s-maxage=60, stale-while-revalidate=300');
---
Server islands
<CartCount server:defer>
<span slot="fallback">0</span>
</CartCount>
Props must be serialisable. The island can read cookies and locals.
Actions
// src/actions/index.ts
import { defineAction, ActionError } from 'astro:actions';
import { z } from 'astro/zod';
export const server = {
subscribe: defineAction({
accept: 'form',
input: z.object({ email: z.email() }),
handler: async ({ email }, context) => {
if (!email.endsWith('.com')) throw new ActionError({ code: 'BAD_REQUEST', message: 'Use a .com address' });
return { email };
},
}),
};
| Call from | How |
|---|---|
| HTML form | <form method="POST" action={actions.subscribe}> on an on-demand page |
| Result in page | Astro.getActionResult(actions.subscribe) |
| Browser script | const { data, error } = await actions.subscribe(formData) |
| Server code | await Astro.callAction(actions.subscribe, input) |
Middleware
// src/middleware.ts
import { defineMiddleware, sequence } from 'astro:middleware';
const auth = defineMiddleware(async (context, next) => {
context.locals.user = await getUser(context.cookies);
if (context.url.pathname.startsWith('/account') && !context.locals.user) {
return context.redirect('/login/');
}
return next();
});
export const onRequest = sequence(auth);
// src/env.d.ts
declare namespace App {
interface Locals { user: { id: string } | null }
}
Always return next() or a Response; see The middleware didn’t return a Response.
Sessions
await Astro.session?.get('cart');
Astro.session?.set('cart', items);
await Astro.session?.regenerate(); // after login
Astro.session?.destroy(); // on logout
// astro.config.mjs (when the adapter has no default driver)
import { sessionDrivers } from 'astro/config';
session: { driver: sessionDrivers.redis({ url: process.env.REDIS_URL }) },
Cookies
Astro.cookies.get('theme')?.value;
Astro.cookies.has('session');
Astro.cookies.set('theme', 'dark', { path: '/', maxAge: 60 * 60 * 24 * 365, httpOnly: false, sameSite: 'lax' });
Astro.cookies.delete('session', { path: '/' });
Endpoints
// src/pages/api/stock.json.ts
export const prerender = false;
export const GET: APIRoute = async ({ url }) => Response.json({ sku: url.searchParams.get('sku') });
export const POST: APIRoute = async ({ request }) => {
const body = await request.json();
return new Response(null, { status: 204 });
};
Redirects and rewrites
return Astro.redirect('/login/', 302);
return Astro.rewrite('/404/');
return context.redirect('/login/'); // middleware/endpoints
Environment variables
import { API_KEY } from 'astro:env/server'; // secrets, server only
import { PUBLIC_SITE } from 'astro:env/client'; // safe for the browser
Error pages
src/pages/404.astro: unmatched routes and empty 404 responsessrc/pages/500.astro: unhandled server errors
Errors you’ll meet
| Message | Fix |
|---|---|
| Cannot use server-side rendering without an adapter | Add an adapter |
| Actions must be used with server output | Adapter + on-demand page |
| Module is only available server-side | Keep server imports out of islands |
For mixed static and server pages, see mixing static and on-demand pages. The on-demand rendering docs cover each adapter.
FAQ
Does Astro SSR work on Cloudflare Pages?
Yes, with @astrojs/cloudflare. The Workers runtime doesn’t support every Node.js API, so check libraries you depend on.
Can a static page use cookies?
Not at build time. Read cookies in a server island, an on-demand page, or client-side JavaScript.