Skip to content

Commit

Permalink
Server-side rendering docs (#255)
Browse files Browse the repository at this point in the history
This documents how to setup and use SSR features in Astro.
  • Loading branch information
matthewp authored Mar 29, 2022
1 parent 1fea8c8 commit c2a0cb9
Show file tree
Hide file tree
Showing 3 changed files with 300 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const SIDEBAR = {
{ text: 'Integrations', link: 'en/guides/integrations-guide' },
{ text: 'RSS', link: 'en/guides/rss' },
{ text: 'UI Frameworks', link: 'en/core-concepts/framework-components' },
{ text: 'Server-side Rendering (experimental)', link: 'en/guides/server-side-rendering' },

{ text: 'Reference', header: true, type: 'api' },
{
Expand All @@ -42,6 +43,7 @@ export const SIDEBAR = {
{ text: 'CLI', link: 'en/reference/cli-reference' },
{ text: 'Runtime API', link: 'en/reference/api-reference' },
{ text: 'Integrations API', link: 'en/reference/integrations-reference' },
{ text: 'Adapter API (experimental)', link: 'en/reference/adapter-reference' },
{ text: 'Routing Rules', link: 'en/core-concepts/routing' },
// ADD: Astro Component Syntax
// ADD: Markdown Syntax
Expand Down
139 changes: 139 additions & 0 deletions src/pages/en/guides/server-side-rendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
layout: ~/layouts/MainLayout.astro
title: Server-side Rendering (experimental)
---

**Server-side Rendering**, aka SSR, is enabled in Astro behind an experimental flag. 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*.

> SSR is marked as __experimental__ in Astro and changes will occur before it becomes stable. Use only if you can handle API changes.
## Enabling SSR in Your Project

To enable SSR you need to use an adapter. The following adapters are available today with more to come in the future:

- [Netlify](https://github.com/withastro/astro/tree/main/packages/integrations/netlify)
- [Node.js](https://github.com/withastro/astro/tree/main/packages/integrations/node)

In this example we will use `@astrojs/netlify` to build for Netlify. First install the adapter:

```bash
npm install --save-dev @astrojs/netlify
```

Once your packages have been installed, add two new lines to your `astro.config.mjs` project configuration file.

```diff
// astro.config.mjs
import { defineConfig } from 'astro/config';
+ import netlify from '@astrojs/netlify/functions';

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

With Netlify you can deploy from git, their web UI, or from the cli. Here we'll use the [Netlify CLI](https://docs.netlify.com/cli/get-started/) to deploy.

First build your site as normal:

```bash
npm run build
```

This creates `netlify/functions/` which contains your SSR code. Deploying your site will deploy this function which contains all of your Astro pages ready to be rendered.

```bash
netlify deploy
```

After the deploy is complete it should provide you a preview URL to see your site.

## Features

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

### Astro.request.headers

The headers for the request are available on `Astro.request.headers`. It is a [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object, a Map-like object where you can retrieve headers such as the cookie.

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

### Astro.redirect

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.

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

### Response

You can also return a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) from any page. You might do this to return a 404 on a dynamic page after looking up an id in the database.

__[id].astro__

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

#### API Routes

A [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) can also be returned from an API route.

```js
import { getProduct } from '../db';

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

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

return new Response(JSON.stringify(product), {
status: 200
});
}
```
159 changes: 159 additions & 0 deletions src/pages/en/reference/adapter-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
---
layout: ~/layouts/MainLayout.astro
title: Astro Adapter API (experimental)
---

Astro is designed to make it easy to deploy to any cloud provider for SSR (server-side rendering). This ability is provided by __adapters__, which are [integrations](/en/reference/integrations-reference/).

> Server-side rendering in Astro is *experimental*. If you are interested in building an adapter for a host now is the perfect time to help shape these APIs. If you are worried about breaking changes this might be a little too soon for you.
## What is an adapter

An adapter is a special kind of [integration](/en/reference/integrations-reference/) that provides an entrypoint for server-side rendering. An adapter does two things:

- Implements host-specific APIs for handling requests.
- Configures the build according to host conventions.

## Building an Adapter

An adapter is an [integration](/en/reference/integrations-reference/) and can do anything that an integration can do.

An adapter __must__ call the `setAdapter` API in the `astro:config:done` hook like so:

```js
export default function createIntegration() {
return {
name: '@matthewp/my-adapter',
hooks: {
'astro:config:done': ({ setAdapter }) => {
setAdapter({
name: '@matthewp/my-adapter',
serverEntrypoint: '@matthewp/my-adapter/server.js'
});
},
},
};
}
```

The object passed into `setAdapter` is defined as:

```ts
interface AstroAdapter {
name: string;
serverEntrypoint?: string;
exports?: string[];
}
```

The properties are:

* __name__: A unique name for your adapter, used for logging.
* __serverEntrypoint__: The entrypoint for server-side rendering.
* __exports__: An array of named exports when used in conjunction with `createExports` (explained below).

### Server Entrypoint

Astro's adapter API attempts to work with any type of host, and gives a flexible way to conform to the host APIs.

#### Exports

Some serverless hosts expect you to export a function, such as `handler`:

```js
export function handler(event, context) {
// ...
}
```

With the adapter API you achieve this by implementing `createExports` in your `serverEntrypoint`:

```js
import { App } from 'astro/app';

export function createExports(manifest) {
const app = new App(manifest);

const handler = (event, context) => {
// ...
};

return { handler };
}
```

And then in your integration, where you call `setAdapter`, provide this name in `exports`:

```diff
export default function createIntegration() {
return {
name: '@matthewp/my-adapter',
hooks: {
'astro:config:done': ({ setAdapter }) => {
setAdapter({
name: '@matthewp/my-adapter',
serverEntrypoint: '@matthewp/my-adapter/server.js',
+ exports: ['handler'],
});
},
},
};
}
```

#### Start

Some hosts expect you to *start* the server yourselves, for example by listening to a port. For these types of hosts, the adapter API allows you to export a `start` function which will be called when the bundle script is run.

```js
import { App } from 'astro/app';

export function start(manifest) {
const app = new App(manifest);

addEventListener('fetch', event => {
// ...
});
}
```

#### astro/app

This module is used for rendering pages that have been prebuilt through `astro build`. Astro uses the standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) and [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects. Hosts that have a different API for request/response should convert to these types in their adapter.

```js
import { App } from 'astro/app';
import http from 'http';

export function start(manifest) {
const app = new App(manifest);

addEventListener('fetch', event => {
event.respondWith(
app.render(event.request)
);
});
}
```

The following methods are provided:

##### app.render(request)

This method calls the Astro page that matches the request, renders it, and returns a Promise to a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object. This also works for API routes, that do not render pages.

```js
const response = await app.render(request);
```

##### app.match(request)

This method is used to determine if a request is matched by the Astro app's routing rules.

```js
if(app.match(request)) {
const response = await app.render(request);
}
```

You can usually call `app.render(request)` without using `.match` because Astro handles 404s if you provide a `404.astro` file. Use `app.match(request)` if you want to handle 404s in a different way.

0 comments on commit c2a0cb9

Please sign in to comment.