Routing
Dynamic Routesの前にNext.jsのルーティングについて簡単に説明します。
Next.jsはプロジェクト直下、もしくはsrcディレクトリ配下のpagesディレクトリに格納されているファイルを自動的にルーティングしてくれます。
例えば、下記のディレクトリ構成があったとします。
src/
|- pages/
|- articles/
| |- index.tsx ①
|- admin/
|- articles/
|- index.tsx ②
本ディレクトリ構成のプロジェクトをデプロイしたウェブサイトでは、https://example.com/articles
にアクセスすれば、①のページがレスポンスされます。その他、https://example.com/admin/articles
にアクセスすれば、③のページがレスポンスされます。
このようにNext.jsではpages配下にディレクトリ構成に基づいて、自動的にルーティングを実現してくれます。
Dynamic Routes
事前に用意できるファイルについては自動的にNext.js側でルーティングしてくれることがわかりました。
それでは事前に用意できないような投稿した記事の詳細画面の場合はどのようにしたら良いでしょうか。
その課題を解決する手段としてNext.jsではDynamic Routesが用意されています。
src/
|- pages/
|- articles/
| |- index.tsx
| |- [id].tsx ☆
|- admin/
|- articles/
|- index.tsx
上記ディレクトリ構成の[id].tsxのようにNext.jsでは角括弧付きでファイルを格納することで動的なルーティングを実現します。
上記がデプロイされたウェブサイトの場合、https://example.com/articles/1
やhttps://example.com/articles/aiueo
が__[id].tsx__をページとしてレスポンスします。
ちなみに[id].tsxでは指定されたパス(=id)の記事の詳細をレスポンスしたいので、下記のコードのようにrouter.queryでパスを取得して利用します。
import { useRouter } from 'next/router';
import { ArticleDetail } from '~/components/atoms/articleDetail';
const ArticleDetails: NextPage = async () => {
const router = useRouter();
const { id } = router.query;
const article = await fetch(`/article/${id}`).then((res) => res.json());
return <ArticleDetail article={article} />;
};
export default ArticleDetails;
getStaticPaths及びgetStaticPropsを利用する方法
Next.jsではgetStaticPropsを利用することでSSGを実現できます。
単純にパスを指定可能なファイルについてはgetStaticPropsを用いて下記のように記述することでSSGを実現します。
src/pages/article/index.tsx
import { NextPage } from 'next';
import { ArticleCard } from '~/components/atoms/articleCard';
import { getAllArticle } from '~/lib/article';
interface Article {
// オブジェクトの型指定
}
type Props = {
articles: Article[];
};
const ArticleList: NextPage<Props> = ({ articles }) => {
return (
<div>
{articles.map((article) => (
<ArticleCard key={article.id} article={article} />
))}
</div>
);
};
export const getStaticProps = async() => {
const articles: Articles[] = await getAllArticle();
return {
props: { articles },
revalidate: 3,
};
}
export default ArticleList;
ビルド時にgetStaticPropsは取得したデータをpropsとして返却します。返却されたpropsはページが受け取って、そのデータを元に静的サイトを生成します。
また、propsと同時に返却しているrevalidate
についてはこちらの記事を参考にしてください。
次に、事前にパスを指定できない記事詳細ページについてはgetStaticPaths
を用いて下記のように記述することでSSGを実現します。
src/pages/article/[id].tsx
import { useRouter } from 'next/router';
import { NextPage } from 'next';
import { ArticleDetail } from '~/components/atoms/articleDetail';
import { ErrorPage } from '~/components/general'
import { getAllArticleId, getArticleById } from '~/lib/article';
interface Article {
// オブジェクトの型指定
}
type Props = {
article: Article;
};
const ArticleDetail: NextPage<Props> = ({ article }) => {
const router = useRouter();
// getStaticPropsでまだデータ取得できていない場合にrouter.isFallbackがtrue
if (router.isFallback) return <div>Loading...</div>;
// データが存在しない場合
if(!article) return <ErrorPage code={404}/>;
return (
<div>
<ArticleDetail article={article}/>
</div>
);
}
export const getStaticPaths = () => {
const paths = await getAllArticleId();
return {
paths,
fallback: true,
};
}
export const getStaticProps = (paths: { params: { id: number } }) => {
const article = await getArticleById(paths.params.id);
return {
props: {
article,
},
revalidate: 3,
};
}
export default ArticleDetail;
このようにgetStaticPropsとは別にgetStaticPathsを定義します。
ビルド時にgetStaticPathsはパスとなるidを取得してpathsとして返却します。
返却されたpathsに1から10までのidが含まれていた場合、1.tsx
や2.tsx
のように__[id]__に1から10が割り当てられたファイルが生成されます。
1.tsxの場合はidとして1
がgetStaticPropsに引数として渡されます。
その後は前項に記載したようにgetStaticPropsは取得したデータをpropsとして返却します。返却されたpropsはページが受け取って、そのデータを元に静的サイトを生成します。
このようにしてSSGの場合もDynamic Routesを実現します。
ちなみに何もしなければ、ビルド後に追加されたidの詳細ページを追加できません。
getStaticPathsの返却する値にfallback
を追加して、trueもしくは'blocking'を指定することでビルドされていないパスに指定された場合に再ビルドさせることができます。
fallbackの詳細については公式サイトをご参照ください。
最後に
記事の内容がわかりづらければ、ご指摘いただけますと幸いです。