サーバーサイドレンダリング
サーバーサイドレンダリング(SSR)は、サーバー上でHTMLページをオンデマンドで生成し、クライアントに送信することを意味します。
SSRを利用すると、次のようなことが可能です。
- アプリのログイン状態のためにセッションを実装する。
fetch
を使って動的にAPIを呼び出しデータをレンダリングする。- アダプターを利用してサイトをホストにデプロイする。
以下が必要な場合は、Astroプロジェクトでサーバーサイドレンダリングを有効にすることを検討してください。
-
APIエンドポイント: SSRにより、クライアントから機密データを隠したまま、データベースへのアクセス、認証、認可などのタスクをおこなうAPIエンドポイントとして機能する特定のページを作成できます。
-
ページの保護: ユーザーの権限に基づいてページへのアクセスを制限する必要がある場合は、SSRを有効にしてサーバーでユーザーアクセスを制御できます。
-
頻繁に変更されるコンテンツ: SSRを有効にすると、サイトを静的に再ビルドすることなく個々のページを生成できます。これはページのコンテンツが頻繁に更新される場合に有効です。
プロジェクトでSSRを有効にする
本番環境でSSRを有効にするには、output
の設定を'server'
または(v2.6.0で導入された)'hybrid'
に更新します。これらのモードは、どのページまたはサーバーエンドポイントをサーバーでレンダリングするかを制御します。これらの設定オプションはデフォルトの動作が異なり、各ルーティングでデフォルトからオプトアウトすることが可能となっています。
output: 'server'
: デフォルトでサーバーレンダリングされます。サイトの大部分またはすべてをサーバーレンダリングする場合に使用します。個別のページやエンドポイントで、事前レンダリングにオプトインできます。output: 'hybrid'
: デフォルトでHTMLに事前レンダリングされます。サイトの大部分を静的にする場合に使用します。個別のページやエンドポイントで、事前レンダリングをオプトアウトできます。
import { defineConfig } from 'astro/config';import nodejs from '@astrojs/node';
export default defineConfig({ output: 'server', adapter: nodejs(),});
エクスポート文により、ページやルートをデフォルトのレンダリング動作からオプトアウトできます。
---export const prerender = true;// ...---<html> <!-- 静的な、事前レンダリングされたページ... --></html>
その他の個別のルートの設定例も確認してください。
静的サイトをハイブリッドレンダリングに変換する
既存の静的なAstroサイトをハイブリッドレンダリングに変換するには、output
を'hybrid'
に変更し、アダプターを追加します。
import { defineConfig } from 'astro/config';import nodejs from '@astrojs/node';
export default defineConfig({ adapter: nodejs({ mode: 'middleware' // または'standalone' }), output: 'hybrid',});
アダプターの追加
SSRを有効にしたプロジェクトをデプロイするには、アダプターを追加する必要があります。SSRはサーバーサイドのコードを実行する環境であるサーバーランタイムが必要なためです。各アダプターによって、Astroは特定のランタイムでプロジェクトを実行するスクリプトを出力できます。
公式版とコミュニティ版のSSRアダプターを、インテグレーションディレクトリで確認できます。
astro add
を使ったインストール
Astro公式のアダプターインテグレーションは、astro add
コマンドを使って追加できます。これにより、アダプターのインストールと、astro.config.mjs
ファイルへの適切な変更が一度に行われます。例えば、Vercelアダプターをインストールするには次のコマンドを実行します。
npx astro add vercel
pnpm astro add vercel
yarn astro add vercel
手動でインストール
パッケージをインストールし、astro.config.mjs
を更新して手動でアダプターを追加することもできます。(以下の2つの手順を完了させSSRを有効にするための各アダプター固有の手順については、上記のリンクをご覧ください。)my-adapter
を何らかのアダプターの例とすると、次のような手順になります。
-
好みのパッケージマネージャーを利用してプロジェクトの依存関係にアダプターをインストールします。
Terminal window npm install @astrojs/my-adapterTerminal window pnpm install @astrojs/my-adapterTerminal window yarn add @astrojs/my-adapter -
astro.config.mjs
ファイルのimportとdefault exportにアダプターを追加します。astro.config.mjs import { defineConfig } from 'astro/config';import myAdapter from '@astrojs/my-adapter';export default defineConfig({output: 'server',adapter: myAdapter(),});
機能
Astroがデフォルトで静的サイトジェネレーターであることは変わりません。しかし、サーバーサイドレンダリングを有効にしてアダプターを追加すると、いくつかの機能を新しく利用できるようになります。
個別のルートの設定
server
とhybrid
モードの両方で、サーバーレンダリングされたページとエンドポイントを使用でき、すべてのページはデフォルトの動作に従ってレンダリングされます。しかし、どちらのモードでも、個別のページをデフォルトの動作からオプトアウトできます。
サーバーレンダリングのオプトアウト
output: server
を設定し大部分がサーバーレンダリングされるアプリでは、ページまたはルートにexport const prerender = true
を追加することで、静的ページまたはエンドポイントを事前レンダリングできます。
---export const prerender = true;// ...---<html> <!-- 静的な、事前レンダリングされたページ... --></html>
---layout: '../layouts/markdown.astro'title: '私のページ'---export const prerender = true;
# これは静的な、事前レンダリングされたページです
エンドポイントの場合は次のようになります。
export const prerender = true;
export async function GET() { return { body: JSON.stringify({ message: `これは静的エンドポイントです` }), };}
事前レンダリングのオプトアウト
output: hybrid
を設定した大部分が静的なサイトでは、export const prerender = false
をサーバーレンダリングさせたいファイルに追加します。
export const prerender = false;
export async function GET() { let number = Math.random(); return { body: JSON.stringify({ number, message: `ランダムな数: ${number}` }), };}
Astro.request.headers
リクエストのヘッダーは、Astro.request.headers
で取得できます。ブラウザのRequest.headers
と同様に動作します。これはHeadersオブジェクトという、Cookieなどのヘッダーを取得できるMapのようなオブジェクトです。
---const cookie = Astro.request.headers.get('cookie');// ...---<html> <!-- ページの内容... --></html>
Astro.request.method
リクエストに使用されたHTTPメソッドは、Astro.request.method
で取得できます。これはブラウザのRequest.method
と同様に動作します。リクエストで使用されたHTTPメソッドを文字列として返します。
---console.log(Astro.request.method) // GET (ブラウザで遷移したとき)---
以下の機能はページレベルでのみ利用できます。(レイアウトコンポーネントを含むコンポーネントの内部では使用できません。)
ブラウザに送信された後に変更することができないレスポンスヘッダーを、以下の機能が変更することが理由です。SSRモードでは、AstroはHTMLストリーミングを使用して、各コンポーネントをレンダリングする際にブラウザに送信します。これによりユーザーにできるだけ早くHTMLを表示できますが、その結果、Astroがコンポーネントのコードを実行する時点で、すでにレスポンスヘッダーが送信されてしまっているのです。
Astro.cookies
単一のCookieを読み取り、変更するためのユーティリティです。Cookieのチェック、設定、取得、削除ができます。
Astro.cookies
とAstroCookie
型 (EN)について詳しくは、APIリファレンスを確認してください。
以下の例では、ページビューカウンターのCookieの値を更新しています。
---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>
Response
ページからResponseを返すこともできます。たとえばデータベースでidを検索した後、動的に404を返す際に利用できます。
---import { getProduct } from '../api';
const product = await getProduct(Astro.params.id);
// 商品(Product)が見つからなかったif (!product) { return new Response(null, { status: 404, statusText: 'Not found' });}---<html> <!-- ページの内容... --></html>
サーバーエンドポイント
APIルートとも呼ばれるサーバーエンドポイントは、src/pages
フォルダの中の.js
, .ts
ファイルからエクスポートされる特別な関数で、Requestを受け取ってResponseを返します。SSRの強力な機能であるAPIルートは、サーバーサイドで安全にコードを実行できます。詳しくはエンドポイントガイドを確認してください。
ストリーミング
ブラウザはHTTPストリーミングをネイティブにサポートしています。ドキュメントはチャンクに分割され、順番にネットワークを介して送信され、その順番でページにレンダリングされます。
この過程でブラウザは、パース、DOMへのレンダリング、描画をおこないながら段階的にHTMLを処理します。このことは意図的にHTMLをストリーミングしているかどうかに関わらず起こります。ネットワークの状況によっては大きなドキュメントのダウンロードが遅れたり、データの取得を待つことでページのレンダリングがブロックされることがあります。
ストリーミングによるページパフォーマンスの改善
以下のページはフロントマターでデータをawait
しています。Astroは、HTMLをブラウザに送信する前に、すべてのfetch
の呼び出しが解決するのを待ちます。
---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>名前と事実</title> </head> <body> <h2>名前</h2> <p>{randomPerson.name.first}</p> <h2>事実</h2> <p>{factData.fact}</p> </body></html>
await
の呼び出しを小さなコンポーネントに移動することで、Astroのストリーミングを活用できます。以下のコンポーネントを使用してデータを取得すると、AstroはまずタイトルなどのHTMLをレンダリングし、そしてデータの準備できたら段落要素をレンダリングします。
---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>
これらのコンポーネントを使用している以下のAstroページは、ページの一部を先にレンダリングできます。<head>
、<body>
、<h1>
タグは、データの取得によってブロックされなくなりました。サーバーはRandomName
とRandomFact
のデータを並列で取得し、その結果となるHTMLをブラウザにストリーミングします。
---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>
Promiseを直接含める
テンプレートに直接Promiseを含めることもできます。こうすると、コンポーネント全体のブロックは起こらず、Promiseを並列で解決してそれに続くマークアップだけがブロックされます。
---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>名前と事実</title> </head> <body> <h2>名前</h2> <p>{personPromise}</p> <h2>事実</h2> <p>{factPromise}</p> </body></html>
この例では、名前
はpersonPromise
とfactPromise
がロードされている間にレンダリングされます。personPromise
が解決されると事実
が表示され、ロードが完了するとfactPromise
もレンダリングされます。