普段、会社ではバックエンドサーバしか触っていないですが、ちょうどフロントエンドを触らせていただけることになりました。ありがたい!!
今のプロジェクトではフロントエンドにReactを使用していますが、同時にNext.jsも使用しています。
フロントを触らせてもらえるようになったといえども、フロントエンド自体に全く詳しくないです。いままで、フロントエンドのことも調査したり、多少触ったりしたことはありますが、頻繁に使わないのですぐ忘れます。
Next.jsも前にちょっと勉強しましたが、ほぼ全部忘れていました。でも流石にそれはいかんと思い、今回記録を残すことで、また全部忘れる未来の私が確認できる様にしてあげようということです。
今回の記録は、Next.jsの公式チュートリアルを行った上で、「SSR(サーバサイドレンダリング)はいつ使うんじゃ」、「実際にどうコードにするんじゃ」を馬鹿な私でもわかるようにまとめることにしました。
なので、その辺の知識を得たい初心者向けの記事になります。正確ではない理解や表現があるかもしれませんが、ご了承ください。
※2022.11 追記|バージョンアップに伴い修正を加えています。
Next.jsとは?
Next.jsとは、公式によると"The React Framework for Production"だそうです。
.....わからん。
まぁReactを使用する上でいろんなことを便利にしてくれるフレームワークなんでしょう。←公式読め。
ちなみに、今回使用している主な目的としては、①SSRを使用することでSEO的、UX的な向上を図りたかったこと、②Routeingをシンプルに実装したかったこと、です。そこだけは間違いないはず。他にもメリットはあるとも思いますが。
Next.jsのPageとは?
チュートリアルの一番に出てきますが、Next.jsではpageと呼ばれるReactコンポーネントが登場します。このpageというのは、js(jsx)もしくはts(tsx)のファイルからexportされたReactコンポーネントのことであり、かつ、/pages/配下のディレクトリに存在するファイルである必要があります。これはかなり重要です。
Next.jsではこのpagesディレクトリ配下が実際のクライアントサイドでのルーティングに値します。
例えば、src(rootDirectory)/pages/posts/first-page.tsなどがあった時、hostname.com/posts/first-pageにアクセスすることでsrc(rootDirectory)/pages/posts/first-page.tsxのページを表示することができます。これはNext.jsがよしなにやってくれるので実装する側はとにかくpages配下に好きなようにディレクトリ(ルーティング)を切れば良いとなります。シンプルでわかりやすいですね。
また、exportするコンポーネント名はなんでも良いですが、必ずexport defaultしないとダメです。
export default function FirstPost() {
return <h1>First Post</h1>
}
Next.jsのLinkコンポーネントとは?
Next.js pagesの勉強が終わると、Linkコンポーネントの話に移ります。
Linkコンポーネントはaタグをラップしたもので、アプリケーション内部でのページ遷移を高速にする、クライアントサイドナビゲーション(Client-side Navigation)です。
Linkコンポーネントを使うことによって、Next.jsではページを読み込むごとにフルリフレッシュをしないのでブラウザで行う(aタブによる)ページ遷移よりも高速に遷移することができます。
<h1 className="title">
Learn <a href="https://nextjs.org">Next.js!</a>
</h1>
Linkコンポーネントには、href(required: pathかURL)を指定し、classNameなどのその他の属性はaタグに指定しなければなりません。
<Link href="/">
<a className="foo" target="_blank" rel="noopener noreferrer">
Hello World
</a>
</h1>
最初に書いた通り、Next.jsのLinkコンポーネントはアプリケーション内部でのページ遷移のために使うものなので外部へのページ遷移に対してはaタグを使用します。
また、hrefはobject的にも記載することができます。
<Link
href={{
pathname: '/about',
query: { name: 'test' },
}}
>
About us
</Link>
さらに、動的なリンク遷移も可能です。例えば、pages/blog/[id].jsは次のリンクの書き方になります。
<Link href={`/blog/${post.id}`}>
{post.title}
</Link>
Code splitting and prefetching
Next.jsはコードスプリッティング(Code splitting)も自動で行ってくれるため、そのページに必要なモジュール(JS, TSファイル)のみをloadします。
また、Next.jsでは、(production modeに限り)Linkコンポーネント先のページのコードもbackgroundでloadしています。(prefetching)。便利だー。
SSR(サーバサイドレンダリング)とは?
SSRの詳しい説明は省きますが、超雑に説明すると、サーバサイドでページ作成(プリレンダリング)してそれをブラウザに返してそのまま画面に表示させよう、そしたらページの表示が早くなるよね、ってやつですね。
ちなみに詳しい内容はこの辺の記事が面白かったです。
Server Side Renderingについて知るべきこと。Server Side Renderingとは何か? それによって何が改善されるのか?(前編) ng-japan 2017
ただのReactだと、ブラウザ上でのリクエスト後の初期ロードの後に、JSをロードしてからReactコンポーネントが初期されアプリがインタラクティブになるのでHTMLのレンダリングにも時間がかかり、結果的にページの表示とかが遅くなります。検索エンジンに情報を拾ってもらえなくなる可能性とかもあるようです。
一方、Next.jsではJSの実行をサーバサイドで行っているため、リクエスト後の初期ロードの段階で、ブラウザ上にはすでにプリレンダリングされたHTMLが来てますから、何かしらが画面に表示されます。その後、JSがロードされアプリがインタラクティブになるので、ページをみている側からすると高速で表示されている様に見えます。
プリレンダリング(pre-rendering)は、Next.jsが(サーバサイドで)前もってHTMLを作成することを意味します。ここで作成されたHTMLはページに関係する最低限のJSのコードしか実行しないので、全体のJSを実行するブラウザ上での動きより高速なのですね。
また、Reactコンポーネントが初期化されアプリがインタラクティブになる過程をハイドレーション(Hydration)と呼びます。
詳しくはこちら
Static Generation(SG)とServer-side Rendering(SSR)
プリレンダリング(pre-rendering)には2つの形式があり、それらの違いは、いつHTMLを生成するかです。
リクエストより先にプリレンダリングが可能ならSGで実装するべきです。例えば、誰がみても同じページの場合です。
一方、頻繁にデータが更新される、リクエスト毎にページのコンテンツが変わる、などの場合はSSRを使用します。
Static Generation(SG)
Static Generationではbuild時にHTMLを生成します。production環境の場合、そのHTMLをリクエストごとに再利用します。CDNでキャッシュすることも可能です。ページのコンテンツが外部からデータを取得する必要がある場合は、getStaticProps関数を使用します。以下は使用例です。
// Blogコンポーネント関数
export default function Blog({ posts }) {
// Render posts...
}
// This function gets called at build time
// export asyncが必須
export async function getStaticProps() {
// Call an external API endpoint to get posts
const res = await fetch('https://.../posts')
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
getStaticPropsはexportされていることが必須で、その理由の一つとして、ページのレンダーよりも先にデータが必要になるから、が挙げられます。
build時にgetStaticPropsが呼ばれ、そのリターン値(propsプロパティをもったオブジェクト)をBlogコンポーネントの引数として受け取るように記述した例が上です。
build時にHTMLを作成しているのであとはリクエストが来る毎にこのHTMLをそのまま返せばいいことになります。
Server-side Rendering(SSR)
Server-side Rendering(SSR)はリクエストを受け付けるたびにHTMLを作成します。リクエスト毎にデータ取得・更新してプリレンダリングしたい場合に使用します。
それ以外の場合、例えば、データを頻繁に更新したいが、プリレンダリングはする必要がない場合はクライアントサイドからデータを取得する実装を入れます。
SSRでの実装例は以下です。getServerSideProps関数を使用してSGみたいに実装できます。getServerSideProps関数はexportされたasync関数であることに気をつけてください。
// Pageコンポーネント
function Page({ data }) {
// Render data...
}
// This gets called on every request
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
export default Page
SSRはSGよりもパフォーマンスが悪いため、絶対に必要でない限りはSSRは使用しないように推奨されています。
Client-side Rendering(CSR)
そもそもプリレンダリングが必要ない場合は、CSRを使用します。
いろんな制約によりクライアントサイドから外部データを取得しなければならない、などの場合はCSRを実装する必要があります。
Next.jsではCSRの実現にデータフェッチのためのReact HookであるSWRを推奨しています。キャッシュの最適化とかに優れている様です。詳しいことはSWR公式をご覧ください。
Dynamic Routes
Dynamic Routesは静的ページで、/post/today, /post/tomorrowみたいな同じ様なルーティングのページをプリレンダリングしたい時に使用します。ブログなどの実装には使えそうですね。
Next.jsでは、getStaticPaths関数を使用してpathを返却します。上の例で言うと、todayとかtomorrowなどのファイル名をローカルや、データベースとかから取得して(自作のgetAllPostTitles)、それをpathsプロパティとして返す実装をすれば、
export async function getStaticPaths() {
const paths = getAllPostTitles()
return {
paths,
fallback: false
}
}
getStaticProps関数の引数でそれを受け取ることができ、それを使用して画面に出したいデータを(自作のgetPostDataより)取得して
propsプロパティに指定してreturnしとけば、
export async function getStaticProps({ params }) {
const postData = getPostData(params.title)
return {
props: {
postData
}
}
}
Postコンポーネントの引数で受け取ることができるので
あとは、よしなに表示したいところに埋めていく。
export default function Post({ postData }) {
return (
<Layout>
{postData.title}
<br />
{postData.id}
<br />
{postData.date}
</Layout>
)
}
みたいな感じです。
Refirects @ SSR
例えば、SSR中に認証を挟んで認証していなかったらログインページへ遷移させたい、という場合、getServerSideProps関数で以下のようなオブジェクトを返せば自動でリダイレクトされるようになります。
以下の例は、SSR時のリダイレクト専用関数の例になります。
export const pageRedirectProps = (path: string): TNextRedirectProps => {
return {
redirect: {
destination: path,
permanent: false,
},
};
};
詳しくはドキュメントを読んでみてください。(疲れた...)
最後に
Next.jsは特殊な特徴を持ってはいますが、使っていると初心者でもわりかしサーバーサイドのことを理解しやすいと思いました。
次は、React Hookの理解かな。(書くとは言っていない。)