コンテンツにスキップ

サーバーサイドレンダリング

サーバーサイドレンダリング(SSR)は、サーバー上でHTMLページをオンデマンドで生成し、クライアントに送信することを意味します。

SSRを利用すると、次のようなことが可能です。

  • アプリのログイン状態のためにセッションを実装する。
  • fetchを使って動的にAPIを呼び出しデータをレンダリングする。
  • アダプターを利用してサイトをホストにデプロイする。

以下が必要な場合は、Astroプロジェクトでサーバーサイドレンダリングを有効にすることを検討してください。

  • APIエンドポイント: SSRにより、クライアントから機密データを隠したまま、データベースへのアクセス、認証、認可などのタスクをおこなうAPIエンドポイントとして機能する特定のページを作成できます。

  • ページの保護: ユーザーの権限に基づいてページへのアクセスを制限する必要がある場合は、SSRを有効にしてサーバーでユーザーアクセスを制御できます。

  • 頻繁に変更されるコンテンツ: SSRを有効にすると、サイトを静的に再ビルドすることなく個々のページを生成できます。これはページのコンテンツが頻繁に更新される場合に有効です。

本番環境でSSRを有効にするには、outputの設定を'server'または(v2.6.0で導入された)'hybrid'に更新します。これらのモードは、どのページまたはサーバーエンドポイントをサーバーでレンダリングするかを制御します。これらの設定オプションはデフォルトの動作が異なり、各ルーティングでデフォルトからオプトアウトすることが可能となっています。

  • output: 'server': デフォルトでサーバーレンダリングされます。サイトの大部分またはすべてをサーバーレンダリングする場合に使用します。個別のページやエンドポイントで、事前レンダリングにオプトインできます。
  • output: 'hybrid': デフォルトでHTMLに事前レンダリングされます。サイトの大部分を静的にする場合に使用します。個別のページやエンドポイントで、事前レンダリングをオプトアウトできます。
astro.config.mjs
import { defineConfig } from 'astro/config';
import nodejs from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: nodejs(),
});

エクスポート文により、ページやルートをデフォルトのレンダリング動作からオプトアウトできます。

src/pages/mypage.astro
---
export const prerender = true;
// ...
---
<html>
<!-- 静的な、事前レンダリングされたページ... -->
</html>

その他の個別のルートの設定例も確認してください。

静的サイトをハイブリッドレンダリングに変換する

セクションタイトル: 静的サイトをハイブリッドレンダリングに変換する

既存の静的なAstroサイトをハイブリッドレンダリングに変換するには、output'hybrid'に変更し、アダプターを追加します。

astro.config.mjs
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公式のアダプターインテグレーションは、astro addコマンドを使って追加できます。これにより、アダプターのインストールと、astro.config.mjsファイルへの適切な変更が一度に行われます。例えば、Vercelアダプターをインストールするには次のコマンドを実行します。

Terminal window
npx astro add vercel

パッケージをインストールし、astro.config.mjsを更新して手動でアダプターを追加することもできます。(以下の2つの手順を完了させSSRを有効にするための各アダプター固有の手順については、上記のリンクをご覧ください。)my-adapterを何らかのアダプターの例とすると、次のような手順になります。

  1. 好みのパッケージマネージャーを利用してプロジェクトの依存関係にアダプターをインストールします。

    Terminal window
    npm install @astrojs/my-adapter
  2. 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がデフォルトで静的サイトジェネレーターであることは変わりません。しかし、サーバーサイドレンダリングを有効にしてアダプターを追加すると、いくつかの機能を新しく利用できるようになります。

serverhybridモードの両方で、サーバーレンダリングされたページとエンドポイントを使用でき、すべてのページはデフォルトの動作に従ってレンダリングされます。しかし、どちらのモードでも、個別のページをデフォルトの動作からオプトアウトできます。

output: serverを設定し大部分がサーバーレンダリングされるアプリでは、ページまたはルートにexport const prerender = trueを追加することで、静的ページまたはエンドポイントを事前レンダリングできます。

src/pages/mypage.astro
---
export const prerender = true;
// ...
---
<html>
<!-- 静的な、事前レンダリングされたページ... -->
</html>
src/pages/mypage.mdx
---
layout: '../layouts/markdown.astro'
title: '私のページ'
---
export const prerender = true;
# これは静的な、事前レンダリングされたページです

エンドポイントの場合は次のようになります。

src/pages/myendpoint.js
export const prerender = true;
export async function GET() {
return {
body: JSON.stringify({ message: `これは静的エンドポイントです` }),
};
}

output: hybridを設定した大部分が静的なサイトでは、export const prerender = falseをサーバーレンダリングさせたいファイルに追加します。

src/pages/randomnumber.js
export const prerender = false;
export async function GET() {
let number = Math.random();
return {
body: JSON.stringify({ number, message: `ランダムな数: ${number}` }),
};
}

リクエストのヘッダーは、Astro.request.headersで取得できます。ブラウザのRequest.headersと同様に動作します。これはHeadersオブジェクトという、Cookieなどのヘッダーを取得できるMapのようなオブジェクトです。

src/pages/index.astro
---
const cookie = Astro.request.headers.get('cookie');
// ...
---
<html>
<!-- ページの内容... -->
</html>

リクエストに使用されたHTTPメソッドは、Astro.request.methodで取得できます。これはブラウザのRequest.methodと同様に動作します。リクエストで使用されたHTTPメソッドを文字列として返します。

src/pages/index.astro
---
console.log(Astro.request.method) // GET (ブラウザで遷移したとき)
---

単一のCookieを読み取り、変更するためのユーティリティです。Cookieのチェック、設定、取得、削除ができます。

Astro.cookiesAstroCookieについて詳しくは、APIリファレンスを確認してください。

以下の例では、ページビューカウンターのCookieの値を更新しています。

src/pages/index.astro
---
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を返すこともできます。たとえばデータベースでidを検索した後、動的に404を返す際に利用できます。

src/pages/[id].astro
---
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の呼び出しが解決するのを待ちます。

src/pages/index.astro
---
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をレンダリングし、そしてデータの準備できたら段落要素をレンダリングします。

src/components/RandomName.astro
---
const personResponse = await fetch('https://randomuser.me/api/');
const personData = await personResponse.json();
const randomPerson = personData.results[0];
---
<p>{randomPerson.name.first}</p>
src/components/RandomFact.astro
---
const factResponse = await fetch('https://catfact.ninja/fact');
const factData = await factResponse.json();
---
<p>{factData.fact}</p>

これらのコンポーネントを使用している以下のAstroページは、ページの一部を先にレンダリングできます。<head><body><h1>タグは、データの取得によってブロックされなくなりました。サーバーはRandomNameRandomFactのデータを並列で取得し、その結果となるHTMLをブラウザにストリーミングします。

src/pages/index.astro
---
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を並列で解決してそれに続くマークアップだけがブロックされます。

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

この例では、名前personPromisefactPromiseがロードされている間にレンダリングされます。personPromiseが解決されると事実が表示され、ロードが完了するとfactPromiseもレンダリングされます。