Overview

Astro defaults to static output: every page is prerendered at build time and served as a flat file. SSR renders pages on each request, which enables personalized content, auth-gated routes, and dynamic data. Astro 5 offers two output modes: static (default; prerenders all pages) and server (SSR by default). The Astro 4 hybrid mode was removed in Astro 5 and merged into static. For most sites, keep static and add an adapter, then opt individual routes into SSR where needed.

Keep output: 'static' and opt routes into SSR

With static (the default) plus an adapter, Astro prerenders every page at build time and serves it from a CDN. A page or endpoint opts into server rendering with one export. This keeps build output minimal and CDN-cacheable for content routes while letting auth endpoints and dynamic pages run on the server.

// astro.config.mjs
import { defineConfig } from "astro/config";
import vercel from "@astrojs/vercel";
 
export default defineConfig({
  // output: "static" is the default; an adapter enables on-demand routes.
  adapter: vercel(),
});

A page or endpoint opts into server rendering with one export:

---
export const prerender = false;
---

Without that export, the page is prerendered at build time. Add it only when the page genuinely needs server rendering. This is the behavior the removed hybrid mode used to provide.

Use output: 'server' when most routes are dynamic

server mode server-renders all pages by default. Use it when the majority of routes require auth, user-specific data, or real-time reads. Opt individual pages back into static with export const prerender = true.

Avoid server mode on a mostly-content site. Prerendering content pages and serving them from a CDN is faster and cheaper than server-rendering them on every request.

Pick an adapter that matches your deployment target

Adapters translate Astro’s server output to the target runtime. Each adapter is installed once and configured as the adapter in astro.config.mjs.

  • Vercel: @astrojs/vercel. The older /serverless and /edge subpath imports were collapsed into the single @astrojs/vercel entry point. See vercel.
  • Netlify: @astrojs/netlify/functions.
  • Cloudflare Pages: @astrojs/cloudflare. Runs on Cloudflare Workers; the cloudflare: module namespace gives access to KV, D1, and R2. See cloudflare.
  • Node.js: @astrojs/node for self-hosted deployments behind a reverse proxy.

Install with astro add to get the correct config automatically:

npx astro add vercel

Running astro build without an adapter configured while output is not static throws an error at build time.

Access request data in server routes

In SSR mode, Astro.request is a standard Request object. Astro.cookies gives access to cookies. Astro.redirect() performs server-side redirects.

---
export const prerender = false;
 
const session = Astro.cookies.get("session");
if (!session) return Astro.redirect("/login");
 
const data = await fetch(`/api/user/${session.value}`).then(r => r.json());
---
<p>Welcome, {data.name}</p>

Avoid Astro.request in prerendered pages; it is undefined at build time and will throw.

Write API endpoints as .ts files in src/pages/

Server endpoints export GET, POST, PUT, DELETE, or ALL functions. They receive a APIContext argument with request, cookies, params, and locals.

// src/pages/api/user.ts
import type { APIRoute } from "astro";
 
export const GET: APIRoute = async ({ cookies }) => {
  const token = cookies.get("token")?.value;
  if (!token) return new Response("Unauthorized", { status: 401 });
  const user = await lookupUser(token);
  return new Response(JSON.stringify(user), {
    headers: { "Content-Type": "application/json" },
  });
};

In static mode with an adapter, endpoint files in src/pages/api/ are prerendered at build time unless they opt into server rendering with export const prerender = false. Add that export to any endpoint that reads request data or runs on each request.