From c2a0cb9b861d50d25c7c9ff9896d130cfb44fde9 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Tue, 29 Mar 2022 07:41:44 -0400 Subject: [PATCH] Server-side rendering docs (#255) This documents how to setup and use SSR features in Astro. --- src/config.ts | 2 + src/pages/en/guides/server-side-rendering.md | 139 ++++++++++++++++ src/pages/en/reference/adapter-reference.md | 159 +++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 src/pages/en/guides/server-side-rendering.md create mode 100644 src/pages/en/reference/adapter-reference.md diff --git a/src/config.ts b/src/config.ts index ae0afa8ec392f..03e1d1162f09a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -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' }, { @@ -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 diff --git a/src/pages/en/guides/server-side-rendering.md b/src/pages/en/guides/server-side-rendering.md new file mode 100644 index 0000000000000..d6559d3fbfcfc --- /dev/null +++ b/src/pages/en/guides/server-side-rendering.md @@ -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'); +// ... +--- + + + +``` + +### 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'); +} +--- + + + +``` + +### 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' + }); +} +--- + + + +``` + +#### 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 + }); +} +``` diff --git a/src/pages/en/reference/adapter-reference.md b/src/pages/en/reference/adapter-reference.md new file mode 100644 index 0000000000000..dc658a596c4ac --- /dev/null +++ b/src/pages/en/reference/adapter-reference.md @@ -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.