komal.nakrani
Browse Themes
ThemesCategoriesDocsBlogTips & Tricks
Server Rendering••By Komal Nakrani

Astro SSR Cheatsheet: Every Server Feature on One Page

Astro SSR cheatsheet: adapters, output modes, prerender, server islands, actions, middleware, sessions, cookies, endpoints and response headers in one page.

#Astro#Cheatsheet#SSR

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' plus export const prerender = false per page is the usual setup.
  • Server islands keep a page static while one component renders per request.
  • Middleware and locals run 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 responses
  • src/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.