---
title: "Astro: Server-Side Rendering"
slug: "astro-ssr"
category: "frontend"
tags: ["frontend", "astro", "ssr", "adapters", "server"]
status: "stable"
last_updated: 2026-08-14
summary: "Switch Astro from static to server output, pick an adapter, and opt individual routes in or out of prerendering with the prerender export."
related: ["[[frontend/astro]]", "[[frontend/astro-integrations]]", "[[frontend/astro-content-collections]]", "[[comparisons/nextjs-vs-astro]]", "[[ops/vercel]]", "[[ops/cloudflare]]"]
---

## 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 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.

```js
// 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:

```astro
---
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 [[ops/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 [[ops/cloudflare]].
- Node.js: `@astrojs/node` for self-hosted deployments behind a reverse proxy.

Install with `astro add` to get the correct config automatically:

```bash
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.

```astro
---
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`.

```ts
// 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.

## Related

- [[frontend/astro]]
- [[frontend/astro-integrations]]
- [[frontend/astro-content-collections]]
- [[comparisons/nextjs-vs-astro]]
- [[ops/vercel]]
- [[ops/cloudflare]]
