Skip to main content
Astro pages are pre-rendered as static HTML by default. You can opt into server-side rendering (SSR) to generate pages on demand when a route is requested.

What is SSR?

Server-side rendering (SSR), also called on-demand rendering, generates HTML pages on the server when they’re requested rather than at build time. This allows you to:
  • Display personalized content
  • Access request data (cookies, headers)
  • Respond to form submissions
  • Serve fresh data without rebuilding

Enabling SSR

SSR requires an adapter for your deployment platform.

Installing an Adapter

Use the Astro CLI to install official adapters:
npx astro add netlify
Available adapters:
  • Node.js
  • Netlify
  • Vercel
  • Cloudflare
  • And more community adapters

Manual Installation

You can also install adapters manually:
npm install @astrojs/netlify
Then add it to your config:
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify';

export default defineConfig({
  adapter: netlify(),
});

Opting into SSR

Per-Page SSR

By default, pages are static. Opt into SSR for specific pages:
---
export const prerender = false;
---
<html>
  <body>
    <h1>This page is server-rendered on demand!</h1>
  </body>
</html>

Server Output Mode

For apps that are mostly dynamic, use output: 'server':
export default defineConfig({
  output: 'server',
  adapter: netlify(),
});
Now all pages are server-rendered by default. Opt out on static pages:
---
export const prerender = true;
---
<html>
  <body>
    <h1>This page is static!</h1>
  </body>
</html>

SSR Features

Cookies

Read and write cookies in server-rendered pages:
---
export const prerender = false;

let counter = 0;

if (Astro.cookies.has('counter')) {
  const cookie = Astro.cookies.get('counter');
  counter = cookie.number() + 1;
}

Astro.cookies.set('counter', String(counter));
---

<html>
  <body>
    <h1>Counter: {counter}</h1>
  </body>
</html>

Response Headers

Set response status and headers:
---
export const prerender = false;

import { getProduct } from '../api';

const product = await getProduct(Astro.params.id);

if (!product) {
  Astro.response.status = 404;
  Astro.response.statusText = 'Not found';
}
---
Set custom headers:
---
export const prerender = false;

Astro.response.headers.set('Cache-Control', 'public, max-age=3600');
---

Request Data

Access request information:
---
export const prerender = false;

const cookie = Astro.request.headers.get('cookie');
const method = Astro.request.method;
const url = Astro.request.url;
---

<p>Method: {method}</p>
<p>URL: {url}</p>

Returning Responses

Return custom Response objects:
---
export const prerender = false;

import { getProduct } from '../api';

const product = await getProduct(Astro.params.id);

if (!product) {
  return new Response(null, {
    status: 404,
    statusText: 'Not found'
  });
}

if (!product.isAvailable) {
  return Astro.redirect('/products', 301);
}
---

<html>
  <body>
    <h1>{product.name}</h1>
  </body>
</html>

Server Endpoints

Create API routes in src/pages/ with .js or .ts files:
export async function GET() {
  return new Response(
    JSON.stringify({
      message: 'Hello, World!'
    })
  );
}
export const prerender = false;

export async function GET() {
  const number = Math.random();
  return new Response(
    JSON.stringify({
      number,
      message: `Here's a random number: ${number}`
    })
  );
}
Endpoints support all HTTP methods:
export async function GET({ params, request }) {
  // Handle GET
}

export async function POST({ request }) {
  const data = await request.json();
  // Handle POST
  return new Response(JSON.stringify({ success: true }));
}

export async function DELETE({ params }) {
  // Handle DELETE
}

HTML Streaming

SSR pages use HTML streaming automatically, sending content to the browser as it’s rendered. This ensures fast Time to First Byte (TTFB).
Features that modify response headers are only available at the page level, not in components.

Hybrid Rendering

Mix static and dynamic pages in the same project:
// Static by default
export default defineConfig({
  output: 'static',
  adapter: netlify(),
});
// Make specific pages dynamic
---
export const prerender = false;
---
Or render most pages dynamically:
// Dynamic by default
export default defineConfig({
  output: 'server',
  adapter: netlify(),
});
// Make specific pages static
---
export const prerender = true;
---

Dynamic Routes with SSR

Static Sites

Use getStaticPaths() to generate routes at build time:
---
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

const { post } = Astro.props;
---

<article>
  <h1>{post.data.title}</h1>
</article>

SSR Sites

Fetch data on demand:
---
import { getEntry } from 'astro:content';

const { slug } = Astro.params;
if (!slug) return Astro.redirect('/404');

const post = await getEntry('blog', slug);
if (!post) return Astro.redirect('/404');
---

<article>
  <h1>{post.data.title}</h1>
</article>

Adapter Configuration

Different adapters may have specific configuration options. Check each adapter’s documentation:
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify';

export default defineConfig({
  adapter: netlify({
    edgeMiddleware: true,
    imageCDN: true,
  }),
});

When to Use SSR

Use SSR when you need:
  • Authentication: Check user sessions and show personalized content
  • Fresh data: Display real-time information without rebuilding
  • User-specific pages: Customize content per visitor
  • Form handling: Process form submissions
  • API routes: Create backend endpoints
  • Cookies and headers: Access request data
Stick with static rendering when:
  • Content doesn’t change per user
  • Performance is critical (static is fastest)
  • You want to deploy to any static host
  • Content only changes when you deploy