Next.js にあるアレ、Astro でどうするか

thumbnail

明日、トロ w

最近 Astro を触ったのでそのときに調べたこと。

Dynamic Routing

Routing 自体は Next.js と同じようにできる。つまり pages/[id].astro のようなものを作れば良い。それが URL になる。 この id は const { id } = Astro.params; のようにして取れる。Next と違って context は無いが、Astro という名前空間から取ってこれる。

getStaticPaths

Next にある getStaticPaths も Astro には getStaticPaths としてある。

export function getStaticPaths() {
  return [
    // Generates: /dogs/clifford
    { params: { dog: "clifford" } },
    // Generates: /dogs/rover
    { params: { dog: "rover" } },
    // Generates: /dogs/spot
    { params: { dog: "spot" } },
  ];
}

のようにして使う。これは /dogs/[dog].astro のような URL のときだ。

FYI: https://docs.astro.build/en/core-concepts/routing/

つまり /[id].astro ならば、

export function getStaticPaths() {
  return [
    // Generates: /dogs/clifford
    { params: { id: "1" } },
    // Generates: /dogs/rover
    { params: { id: "2" } },
    // Generates: /dogs/spot
    { params: { id: "3" } },
  ];
}

とする必要がある。

FYI: https://docs.astro.build/en/reference/api-reference/#params

getStaticProps

Next の getStaticProps 相応のものは Astro だと getStaticPaths にまとめられる。params 以外にも props を返すことができて、

export async function getStaticPaths() {
  const data = await fetch("...").then((response) => response.json());

  return data.map((post) => {
    return {
      params: { id: post.id },
      props: { post },
    };
  });
}

const { id } = Astro.params;
const { post } = Astro.props;

のようにして返すことができる。

FYI: https://docs.astro.build/en/reference/api-reference/#data-passing-with-props

この機能は実はとてもうれしくて、例えば NextJS であれば /posts/:id のページを作るためには id 一覧を getStaticPaths で取った後は、その posts の数だけ getStaticPropsで data を取る必要がある。

例えば、

export async function getStaticPaths() {
  return {
    paths: [{ params: { id: "1", hoge: 2 } }, { params: { id: "2", hoge: 3 } }],
    fallback: false, // can also be true or 'blocking'
  };
}

// `getStaticPaths` requires using `getStaticProps`
export async function getStaticProps(context) {
  return {
    // Passed to the page component as props
    props: {
      post: {
        data: JSON.stringify(context),
      },
    },
  };
}

export default function Post({ post }) {
  return <div>{JSON.stringify(post)}</div>;
}

のようにして paths と一緒にデータを渡すことができないのである。 (ちなみに先のコードは {"data":"{\"params\":{\"id\":\"1\"}}"} と表示される。) つまり毎回 IO へのアクセスが発生するのだが、Astro だともし id と一緒にデータの一覧も返せば 1 回の IO で全ページを作れるのである。

export async function getStaticPaths() {
  const data = await fetch("...").then((response) => response.json());

  return data.map((post) => {
    return {
      params: { id: post.id },
      props: { post },
    };
  });
}

const { id } = Astro.params;
const { post } = Astro.props;

これはかなり嬉しい。

_app.tsx

Next の _app.tsx, もしくは Gatsby の gatsby-browser.js のようなクライアントサイドで必ず呼ばれるコードはどう実現したらいいか。例えば reset.css の読み込みだったり firebase の初期化に使う。このやり方はもしかしたらいくつかあるかもしれないが、自分はそれ用の template を作った。

<script type="module">
  // Import the functions you need from the SDKs you need
  import { initializeApp } from "https://www.gstatic.com/firebasejs/9.10.0/firebase-app.js";
  import { getAnalytics } from "https://www.gstatic.com/firebasejs/9.10.0/firebase-analytics.js";
  // TODO: Add SDKs for Firebase products that you want to use
  // https://firebase.google.com/docs/web/setup#available-libraries

  // Your web app's Firebase configuration
  // For Firebase JS SDK v7.20.0 and later, measurementId is optional
  const firebaseConfig = {
    apiKey: "hoge",
    authDomain: "hoge-hoge.firebaseapp.com",
    projectId: "hoge-hoge",
    storageBucket: "hoge-hoge.appspot.com",
    messagingSenderId: "hoge",
    appId: "1:hoge:web:hoge",
    measurementId: "G-hoge",
  };

  // Initialize Firebase
  const app = initializeApp(firebaseConfig);
  const analytics = getAnalytics(app);
</script>
<slot />

<slot /> がミソで、これがあることで入れ子レイアウトとして定義できるようになる。あとはこれを全ページで囲んであげると良い。

---
import Firebase from "../../layouts/Firebase.astro";
import Layout from "../../layouts/Layout.astro";
---

<Layout title="hello">
  <Firebase>
    <h1>hello</h1>
    <style>
      h1 {
        width: 100%;
      }
    </style>
  </Firebase>
</Layout>