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.
  • Deploy your site to a host using an adapter.

To get started, enable SSR features in development mode with the output: server configuration option:

astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
  output: 'server'
});

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:

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. If you’re using npm or aren’t sure, run this in the terminal:

    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 a server-side rendering adapter, every route in your pages directory becomes a server-rendered route and a few new features become available to you.

The headers for the request are available on Astro.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>

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

src/pages/account.astro
---
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>

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>

An API route is a .js or .ts file within the src/pages folder that takes a Request and returns a Response. A powerful feature of SSR, API routes are able to securely execute code on the server side.

In Astro, these routes turn into server-rendered routes, allowing you to use features that were previously unavailable on the client side, or required explicit calls to a backend server and extra client code to render the results.

In the example below, an API route is used to retrieve a product from a database, without having to generate a page for each of the options.

src/pages/[id].js
import { getProduct } from '../db';

export async function get({ params }) {
  const { id } = params;
  const product = await getProduct(id);

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

  return new Response(JSON.stringify(product), {
    status: 200
  });
}

In this example, a valid HTML code can be returned to render the whole page or some of its content.

In addition to content fetching and server-side rendering, API routes can be used as REST API endpoints to run functions such as authentications, database access, and verifications without exposing sensitive data to the client.

In the example below, an API route is used to verify Google reCaptcha v3 without exposing the site-secret to the clients.

src/pages/index.astro
<html>
  <head>
    <script src="https://www.google.com/recaptcha/api.js"></script>
  </head>

  <body>
    <button class="g-recaptcha" 
      data-sitekey="PUBLIC_SITE_KEY" 
      data-callback="onSubmit" 
      data-action="submit"> Click me to verify the captcha challenge! </button>

    <script is:inline>
      function onSubmit(token) {
        fetch("/recaptcha", {
          method: "POST",
          body: JSON.stringify({ recaptcha: token })
        })
        .then((response) => response.json())
        .then((gResponse) => {
          if (gResponse.success) {
            // Captcha verification was a success
          } else {
            // Captcha verification failed
          }
        })
      }
    </script>
  </body>
</html>

In the API route you can safely define secret values, or read your secret environment variables.

src/pages/recaptcha.js
import fetch from 'node-fetch';

export async function post({ request }) {
  const data = await request.json();

  const recaptchaURL = 'https://www.google.com/recaptcha/api/siteverify';
  const requestBody = {
    secret: "YOUR_SITE_SECRET_KEY",   // This can be an environment variable
    response: data.recaptcha          // The token passed in from the client
  };

  const response = await fetch(recaptchaURL, {
    method: "POST",
    body: JSON.stringify(requestBody)
  });

  const responseData = await response.json();

  return new Response(JSON.stringify(responseData), { status: 200 });
}

Since Astro.redirect is not available in API Routes you can use Response.redirect.

src/pages/links/[id].js
import { getLinkUrl } from '../db';

export async function get({ params }) {
  const { id } = params;
  const link = await getLinkUrl(id);

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

  return Response.redirect(link, 307);
}

Response.redirect requires that you pass a full URL. For local redirects, you can use request.url as the base with the URL constructor to build an absolute URL:

src/pages/redirect.js
export async function get({ request }) {
  const url = new URL('/home', request.url);
  return Response.redirect(url, 307);
}