[Astro] 静的サイトジェネレーター [エンドポイント、データフェッチ]

静的アセット

Astro は、ほとんどの静的アセットを設定不要でサポートしています。プロジェクトの JavaScript (Astro front-matter スクリプトを含む) のどこでも import 文を使用でき、Astro は最終ビルドにその静的アセットのビルドされた最適化されたコピーを含めます。また、@import は CSS と <style> タグの中でもサポートされています。

エンドポイント

Astro では、あらゆる種類のデータを提供するためのカスタムエンドポイントを作成できます。これを利用して、画像を生成したり、RSS を公開したり、または API ルーティングとして使用してサイトの完全な API を構築できます。

静的ファイルのエンドポイント

カスタムエンドポイントを作成するには、.js または .ts ファイルを /pages ディレクトリに追加してください。
src/pages/builtwith.json.ts

// 出力: /builtwith.json
export async function get({ params, request }) {
  return {
    body: JSON.stringify({
      name: 'Astro',
      url: 'https://astro.build/',
    }),
  };
}

戻り値のオブジェクトは、encoding プロパティを持つことができます。
src/pages/astro-logo.png.ts

export async function get({ params, request }) {
  const response = await fetch("https://astro.build/assets/press/full-logo-light.png");
  const buffer = Buffer.from(await response.arrayBuffer());
  return {
    body: buffer,
    encoding: 'binary',
  };
}

また、APIRoute 型を使用してエンドポイント関数に型付けもできます。

import type { APIRoute } from 'astro';

export const get: APIRoute = async function get ({ params, request }) {
...

params と動的ルーティング

エンドポイントは、ページと同じ動的ルーティング機能をサポートしています。ファイル名を括弧付きのパラメーター名とし、getStaticPaths() 関数 (EN) をエクスポートしてください。そして、エンドポイント関数に渡された params プロパティを使用して、パラメーターにアクセスします。
src/pages/[id].json.ts

import type { APIRoute } from 'astro';

const usernames = ["Sarah", "Chris", "Dan"]

export const get: APIRoute = ({ params, request }) => {
  const id = params.id;
  return {
    body: JSON.stringify({
      name: usernames[id]
    })
  }
};

export function getStaticPaths () {
  return [
    { params: { id: "0"} },
    { params: { id: "1"} },
    { params: { id: "2"} },
  ]
}

request

すべてのエンドポイントは request プロパティを受け取りますが、静的モードでは request.url にのみアクセスが可能です。
src/pages/request-path.json.ts

import type { APIRoute } from 'astro';

export const get: APIRoute = ({ params, request }) => {
  return {
    body: JSON.stringify({
      path: new URL(request.url).pathname
    })
  };
}

サーバーエンドポイント(APIルーティング)

静的ファイルエンドポイントのセクションで説明したものはすべて、SSRモードでも使用できます。
サーバーエンドポイントは、getStaticPathsをエクスポートせずparamsにアクセスでき、Responseオブジェクトを返せるので、ステータスコードやヘッダーを設定できます。
src/pages/[id].json.js

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

export async function get({ params }) {
  const id = params.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,
    headers: {
      "Content-Type": "application/json"
    }
  });
}

HTTPメソッド

get 関数に加え、任意の HTTP メソッド名を持つ関数をエクスポートできます。リクエストが来ると、Astroはそのメソッドをチェックして、対応する関数を呼び出します。
src/pages/methods.json.ts

export const get: APIRoute = ({ params, request }) => {
  return {
    body: JSON.stringify({
      message: "This was a GET!"
    })
  }
};

export const post: APIRoute = ({ request }) => {
  return {
    body: JSON.stringify({
      message: "This was a POST!"
    })
  }
}

export const del: APIRoute = ({ request }) => {
  return {
    body: JSON.stringify({
      message: "This was a DELETE!"
    })
  }
}

export const all: APIRoute = ({ request }) => {
  return {
    body: JSON.stringify({
      message: `This was a ${ request.method }!`
    })
  }
}

request

SSR モードでは、request プロパティは、現在のリクエストを参照する完全に使用可能な Request オブジェクトを返します。
src/pages/test-post.json.ts

export const post: APIRoute = async ({ request }) => {
  if (request.headers.get("Content-Type") === "application/json") {
    const body = await request.json();
    const name = body.name;
    return new Response(JSON.stringify({
      message: "Name: " + name
    }), {
      status: 200
    })
  }
  return new Response(null, { status: 400 });
}

リダイレクト

エンドポイントコンテキストは、Astro.redirect に似た redirect() ユーティリティをエクスポートします。
src/pages/links/[id].js

import { getLinkUrl } from '../db';

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

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

  return redirect(link, 307);
}

データフェッチ

Astroでの fetch()

すべての Astro コンポーネントは、そのコンポーネントスクリプトでグローバルな fetch() function にアクセスし、API に HTTP リクエストを行えます。この fetch はビルド時に実行され、そのデータは動的な HTML を生成するためコンポーネントテンプレートで利用可能になります。
src/components/User.astro

---
import Contact from '../components/Contact.jsx';
import Location from '../components/Location.astro';

const response = await fetch('https://randomuser.me/api/');
const data = await response.json();
const randomUser = data.results[0]
---
<!-- ビルド時に取得したデータでHTMLがレンダリングされます -->
<h1>ユーザー</h1>
<h2>{ randomUser.name.first } { randomUser.name.last }</h2>

<!-- ビルド時に取得したデータがpropsとしてコンポーネントに渡されます。 -->
<Contact client:load email={ randomUser.email } />
<Location city={ randomUser.location.city } />

UIフレームワークでの fetch()

fetch() 関数は、任意のUI フレームワークからもグローバルに利用できます。
Movies.tsx

import type { FunctionalComponent } from 'preact';
import { h } from 'preact';

const data = await fetch('https://example.com/movies.json').then((response) =>
  response.json()
);

// ビルド時にレンダリングされるコンポーネントはCLIにもログとして出力されます
// client:*ディレクティブでレンダリングされた場合、ブラウザコンソールにもログが出力されます
console.log(data);

const Movies: FunctionalComponent = () => {
  // 結果をページに出力する
  return <div>{ JSON.stringify(data) }</div>;
};

export default Movies;