Server-side Rendering
Server-side Rendering, aka SSR, can be enabled in Astro. When you enable SSR you can:
- Implement sessions for login state in your app.
- Render data from an API called dynamically with
fetch
.@version 2.0.0 - Deploy your site to a host using an adapter.
Enabling SSR in Your Project
Section titled Enabling SSR in Your ProjectTo get started, enable SSR features in development mode with the output: server
configuration option:
import { defineConfig } from 'astro/config';
export default defineConfig({
output: 'server'
});
Adding an Adapter
Section titled Adding an AdapterWhen 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:
astro add
Install
Section titled astro add InstallYou 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:
npx astro add netlify
pnpm astro add netlify
yarn astro add netlify
Manual Install
Section titled Manual InstallYou 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:
-
Install the adapter to your project dependencies using your preferred package manager:
Terminal window npm install @astrojs/my-adapter
Terminal window pnpm install @astrojs/my-adapter
Terminal window yarn add @astrojs/my-adapter
-
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(), });
Features
Section titled FeaturesAstro will remain a static-site generator by default. But once you enable a server-side rendering adapter, every route in your pages directory defaults to a server-rendered route and a few new features become available to you.
Hybrid Rendering
Section titled Hybrid Renderingastro@2.0.0
Any individual page or server endpoint that supports exporting variables (.astro
, .mdx
, .ts
, or .js
) can opt in to pre-rendering when your Astro project is configured to use SSR with output: server
. These files will be statically rendered at build-time, similar to the default output: static
mode.
Simply add export const prerender = true
:
---
export const prerender = true;
// ...
---
<html>
<!-- Page here... -->
</html>
---
layout: '../layouts/markdown.astro'
title: 'My page'
---
export const prerender = true;
# This is my page
export const prerender = true;
export async function get() {
return {
body: JSON.stringify({ message: `This is my endpoint` }),
};
}
Static pre-rendering by default
Section titled Static pre-rendering by defaultastro@2.5.0
New
You may have a site that is mostly static and can benefit from pre-rendering, but only a few of your pages or endpoints need to be server-rendered. SSR in Astro defaults to rendering on the server and requires you to individually designate the pages that should be static.
To avoid declaring most of your pages as pre-rendered, change the output
from 'server'
to 'hybrid'
and take advantage of SSR with static pre-rendering by default:
export { defineConfig } from 'astro/config';
export default defineConfig({
output: 'hybrid',
experimental: {
hybridOutput: true,
},
});
With this configuration, all of your pages and API endpoints will be pre-rendered, even though your project is configured with an adapter to use SSR. You can then opt-out of pre-rendering any individual file or route by adding export const prerender = false
to any files that should be server-rendered.
export const prerender = false;
export async function get() {
let number = Math.random();
return {
body: JSON.stringify({ number, message: `Here's a random number: ${number}` }),
};
}
Astro.request.headers
Section titled Astro.request.headersThe 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.
---
const cookie = Astro.request.headers.get('cookie');
// ...
---
<html>
<!-- Page here... -->
</html>
Astro.request.method
Section titled Astro.request.methodThe 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.
---
console.log(Astro.request.method) // GET (when navigated to in the browser)
---
Astro.cookies
Section titled Astro.cookiesThis 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.
---
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>
Astro.redirect
Section titled Astro.redirectOn the Astro
global, this method allows you to redirect to another page. You might do this after checking if the user is logged in by getting their session from a cookie.
---
import { isLoggedIn } from '../utils';
const cookie = Astro.request.headers.get('cookie');
// If the user is not logged in, redirect them to the login page
if (!isLoggedIn(cookie)) {
return Astro.redirect('/login');
}
---
<html>
<!-- Page here... -->
</html>
Response
Section titled ResponseYou 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.
---
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>
Server Endpoints
Section titled Server EndpointsA 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.
Streaming
Section titled StreamingBrowsers 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 performanceThe following page await
s some data in its frontmatter. Astro will wait for all of the fetch
calls to resolve before sending any HTML to the browser.
---
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.
---
const personResponse = await fetch('https://randomuser.me/api/');
const personData = await personResponse.json();
const randomPerson = personData.results[0];
---
<p>{randomPerson.name.first}</p>
---
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 browser will then wait for RandomName
to finish loading its Promises, and then send the resulting HTML over the network. It will render the next heading, and then it will wait for RandomFact
and send its resulting HTML over the browser.
---
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>
Including Promises directly
Section titled Including Promises directlyYou can also include promises directly in the template. Instead of blocking the entire component, it will only block the markup that comes after it.
---
const personPromise = fetch('https://randomuser.me/api/')
.then(resp => response.json())
.then(arr => arr[0].name.first);
const factPromise = fetch('https://catfact.ninja/fact')
.then(resp => 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
is loading. At that point, A fact
will be appear, while factPromise
is loading.