Server⁠-⁠side Rendering

Server-side rendering (SSR) refers to generating HTML pages on the server on-demand and sending them to the client.

SSR allows you to:

  • Implement sessions for login state in your app.
  • Render data from an API called dynamically with fetch.
  • Deploy your site to a host using an adapter.

Consider enabling server-side rendering in your Astro project if you need the following:

  • API endpoints: SSR enables you to create specific pages that function as API endpoints for tasks like database access, authentication, and authorization while keeping sensitive data hidden from the client.

  • Protected pages: If you need to restrict access to a page based on user privileges, you can enable SSR to handle user access on the server.

  • Frequently changing content: Enabling SSR lets you generate individual pages without requiring a static rebuild of your site. This is useful when the content of a page updates frequently.

To enable SSR features for production deployments, update your output configuration to 'server' or 'hybrid' (introduced in v2.6.0). Both of these modes control which pages or server endpoints should be server-rendered. Each configuration option has a different default behavior, and allows individual routes to opt-out of the default accordingly:

  • output: 'server': Server-rendered by default. Use this when most or all of your site should be server-rendered. Any individual page or endpoint can opt-in to pre-rendering.
  • output: 'hybrid': Pre-rendered to HTML by default. Use this when most of your site should be static. Any individual page or endpoint can opt-out of pre-rendering.
astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
output: 'server'
});

You can then opt-out of the default rendering behavior with an export statement in any page or route:

src/pages/mypage.astro
---
export const prerender = true;
// ...
---
<html>
<!-- Static, pre-rendered page here... -->
</html>

See more usage examples of configuring individual routes

Converting a static site to hybrid rendering

Section titled Converting a static site to hybrid rendering

To convert an existing static Astro site to allow hybrid rendering, change the output to 'hybrid' and add an adapter:

import { defineConfig } from 'astro/config';
import nodejs from '@astrojs/node';
export default defineConfig({
adapter: nodejs(),
output: 'hybrid',
});

When it’s time to deploy an SSR project, you also need to add an adapter. This is because SSR requires a server runtime: the environment that runs your server-side code. Each adapter allows Astro to output a script that runs your project on a specific runtime.

The following adapters are available today with more to come in the future:

You can add any of the official adapters with the following astro add command. This will install the adapter and make the appropriate changes to your astro.config.mjs file in one step. For example, to install the Netlify adapter, run:

Terminal window
npx astro add netlify

You can also add an adapter manually by installing the package and updating astro.config.mjs yourself. (See the links above for adapter-specific instructions to complete the following two steps to enable SSR.) Using my-adapter as an example placeholder, the instructions will look something like:

  1. Install the adapter to your project dependencies using your preferred package manager:

    Terminal window
    npm install @astrojs/my-adapter
  2. Add the adapter to your astro.config.mjs file’s import and default export:

    astro.config.mjs
    import { defineConfig } from 'astro/config';
    import myAdapter from '@astrojs/my-adapter';
    export default defineConfig({
    output: 'server',
    adapter: myAdapter(),
    });

Astro will remain a static-site generator by default. But once you enable server-side rendering and add an adapter, a few new features become available to you.

Both server and hybrid modes allow for server-rendered pages and endpoints and will render all pages according to their default mode. However, both modes allow you to mark an individual page to opt-out of this default behavior.

Opting-out of server-rendering

Section titled Opting-out of server-rendering

For a mostly server-rendered app configured as output: server, add export const prerender = true to any page or route to pre-render a static page or endpoint:

src/pages/mypage.astro
---
export const prerender = true;
// ...
---
<html>
<!-- Static, pre-rendered page here... -->
</html>
src/pages/mypage.mdx
---
layout: '../layouts/markdown.astro'
title: 'My page'
---
export const prerender = true;
# This is my static, pre-rendered page

And for an endpoint:

src/pages/myendpoint.js
export const prerender = true;
export async function get() {
return {
body: JSON.stringify({ message: `This is my static endpoint` }),
};
}

For a mostly static site configured as output: hybrid, add export const prerender = false to any files that should be server-rendered:

src/pages/randomnumber.js
export const prerender = false;
export async function get() {
let number = Math.random();
return {
body: JSON.stringify({ number, message: `Here's a random number: ${number}` }),
};
}

The headers for the request are available on Astro.request.headers. This works like the browser’s Request.headers. It is a Headers object, a Map-like object where you can retrieve headers such as the cookie.

src/pages/index.astro
---
const cookie = Astro.request.headers.get('cookie');
// ...
---
<html>
<!-- Page here... -->
</html>

The HTTP method used in the request is available as Astro.request.method. This works like the browser’s Request.method. It returns the string representation of the HTTP method used in the request.

src/pages/index.astro
---
console.log(Astro.request.method) // GET (when navigated to in the browser)
---

This is a utility to read and modify a single cookie. It allows you to check, set, get and delete a cookie.

See more details about Astro.cookies and the AstroCookie type in the API reference.

The example below updates the value of a cookie for a page view counter.

src/pages/index.astro
---
let counter = 0
if(Astro.cookies.has("counter")){
const cookie = Astro.cookies.get("counter")
counter = cookie.number() + 1
}
Astro.cookies.set("counter",counter)
---
<html>
<h1>Counter = {counter}</h1>
</html>

You can also return a Response from any page. You might do this to return a 404 on a dynamic page after looking up an id in the database.

src/pages/[id].astro
---
import { getProduct } from '../api';
const product = await getProduct(Astro.params.id);
// No product found
if (!product) {
return new Response(null, {
status: 404,
statusText: 'Not found'
});
}
---
<html>
<!-- Page here... -->
</html>

A server endpoint, also known as an API route, is a special function exported from a .js or .ts file within the src/pages/ folder. The function takes an endpoint context and returns a Response. A powerful feature of SSR, API routes are able to securely execute code on the server. To learn more, see our Endpoints Guide.

Browsers natively support HTTP streaming, where a document is broken up into chunks, sent over the network in order, and rendered on the page in that order.

During this process, browsers consume HTML incrementally: parsing, rendering into the DOM, and painting. This happens whether or not you intentionally stream your HTML. Network conditions can cause large documents to be downloaded slowly, and waiting for data fetches can block page rendering.

Using streaming to improve page performance

Section titled Using streaming to improve page performance

The following page awaits some data in its frontmatter. Astro will wait for all of the fetch calls to resolve before sending any HTML to the browser.

src/pages/index.astro
---
const personResponse = await fetch('https://randomuser.me/api/');
const personData = await personResponse.json();
const randomPerson = personData.results[0];
const factResponse = await fetch('https://catfact.ninja/fact');
const factData = await factResponse.json();
---
<html>
<head>
<title>A name and a fact</title>
</head>
<body>
<h2>A name</h2>
<p>{randomPerson.name.first}</p>
<h2>A fact</h2>
<p>{factData.fact}</p>
</body>
</html>

Moving the await calls into smaller components allows you to take advantage of Astro’s streaming. Using the following components to perform the data fetches, Astro can render some HTML first, such as the title, and then the paragraphs when the data is ready.

src/components/RandomName.astro
---
const personResponse = await fetch('https://randomuser.me/api/');
const personData = await personResponse.json();
const randomPerson = personData.results[0];
---
<p>{randomPerson.name.first}</p>
src/components/RandomFact.astro
---
const factResponse = await fetch('https://catfact.ninja/fact');
const factData = await factResponse.json();
---
<p>{factData.fact}</p>

The Astro page below using these components can render parts of the page sooner. The <head>, <body>, and <h1> tags are no longer blocked by data fetches. The server will then fetch data for RandomName and RandomFact in parallel and stream the resulting HTML to the browser.

src/pages/index.astro
---
import RandomName from '../components/RandomName.astro'
import RandomFact from '../components/RandomFact.astro'
---
<html>
<head>
<title>A name and a fact</title>
</head>
<body>
<h2>A name</h2>
<RandomName />
<h2>A fact</h2>
<RandomFact />
</body>
</html>

You can also include promises directly in the template. Instead of blocking the entire component, it will resolve the promise in parallel and only block the markup that comes after it.

src/pages/index.astro
---
const personPromise = fetch('https://randomuser.me/api/')
.then(response => response.json())
.then(arr => arr[0].name.first);
const factPromise = fetch('https://catfact.ninja/fact')
.then(response => response.json())
.then(factData => factData.fact);
---
<html>
<head>
<title>A name and a fact</title>
</head>
<body>
<h2>A name</h2>
<p>{personPromise}</p>
<h2>A fact</h2>
<p>{factPromise}</p>
</body>
</html>

In this example, A name will render while personPromise and factPromise are loading. Once personPromise has resolved, A fact will appear and factPromise will render when it’s finished loading.

Split the SSR build into multiple files

Section titled Split the SSR build into multiple files

By default, Astro builds all pages into one single file called entry.mjs. You can configure split in your build options to instead emit a single file for each page:

astro.config.mjs
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
build: {
split: true
}
});

The emitted page files will be saved in the directory specified for outDir, under a newly created pages/ directory.

The files inside the pages/ directory of the build will mirror the directory structure of your page files in src/pages/, for example:

  • Directorydist/
    • Directorypages/
      • Directoryblog/
        • entry._slug_.astro.mjs
        • entry.about.astro.mjs
      • entry.index.astro.mjs