Next.js チュートリアルまとめ
Create a Next.js App
Next.js のメリット
Next.js は React のフレームワークである。
React で Web アプリを構築するには以下のことを考慮する必要があるが、Next.js ではそれらを全て解決できる(らしい)。
- Babel や Webpack などの環境設定
- Babel:最新の構文を古いブラウザでも動くように変換するツール
- Webpack:ファイル同士の依存関係を1つにまとめてくれるツール
- コード分割など、プロダクションの最適化
- コード分割:バンドルしたファイルを自動的に分割して、パフォーマンスを最適化する
- パフォーマンスや SEO のための Pre-render 設定
- Pre-rendering:事前にコードを読み込んでおくこと
- ↑により、表示を高速化できる
- Rendering のタイミング(ページによって変える)
- Server-Side Rendering(SSR):サーバー側でレンダリングしておく
- レンダリングしたページをクローラーに見せられるため、SEO 的に強くなる
- クローラー:検索エンジンにおける検索の順位づけに必要な要素をアプリを見て、収集してくるロボット
- Client-Side Rendering(CSR):ブラウザ側でレンダリングする
- 通常の React ではこちらのみ
- クローラーが CSR のページを見るときは、空のページなので SEO 的には弱い
- (Static Generation(SG))
- 詳しくは後ほど
- Server-Side Rendering(SSR):サーバー側でレンダリングしておく
- サーバーサイドの処理
- API を簡単に作れる
環境構築
前提
- Node.js のバージョン:10.13 以降
- 以下のコマンドで調べられる
node -v
Create a Next.js app
自分の作業場所にて、以下のコマンドを実行する。
npx create-next-app@latest `アプリ名` --use-npm --example "https://github.com/vercel/next-learn/tree/master/basics/learn-starter"
開発用サーバーを起動
- 新しく作成されたディレクトリに移動
cd `アプリ名`
- 開発用サーバーを立ち上げる
npm run dev
以下のページが表示されれば成功!
ページ遷移
概要
-
pages
ディレクトリ内に作られたファイルが Web サイトの1ページとなり、そのファイル名が URL に関連づけられる -
Link
コンポーネントを使用すると、ページ間でクライアントサイドナビゲーション[1]を可能にする - Next.js はコードを自動的に分割するため、各ページはそのページに必要なものだけが読み込まれる
Next.js内のページ
- Next.js では、
pages
ディレクトリ内にファイルを作れば、Webサイトの1ページとなる - ファイル名に基づいて、 URL も関連づけられる
- 「/」:pages/index.js
- 「posts/first-post.js」:pages/posts/first-post.js
const Home = () => {
return <div>Home</div>
}
export default Home
const FirstPost = () => {
return <div>FirstPost</div>
}
export default FirstPost
Link コンポーネント
Next.js では、すでに用意されている Link
コンポーネントを使用すると、アプリのページ間をリンクすることができ、クライアントサイドナビゲーションを可能にする
import Link from 'next/link';
先ほど作成したコードでページ遷移を実行してみる
import Link from 'next/link'
const Home = () => {
return (
<>
<div>Home</div>
<Link href="/posts/first-post">Go to FirstPost</Link>
</>
)
}
import Link from 'next/link'
const FirstPost = () => {
return (
<>
<div>FirstPost</div>
<Link href="/">Back to Home</Link>
</>
)
}
コード分割と prefetching
コード分割
Next.js はコードを自動的に分割するため、各ページはそのページに必要なものだけが読み込まれる。
それにより、以下の2つの恩恵を得ることができる。
- たくさんのページがあっても高速に読み込むことが可能
- あるページがエラーを起こしても残りは正常に動作する
prefetching
Next.js の本番ビルドでは、Link
コンポーネントが存在する場合、自動的に prefetch するようになっている。すなわち、Link
コンポーネントでリンクされたコードを事前に取得している。
それにより、高速にページ遷移が可能となる。
-
JavaScript を使用したページ遷移で、URL を切り替えてもページ再読み込みが不要だったり、ブラウザのページ遷移よりも高速になる ↩︎
アセットやメタデータ、CSS
アセット
Next.js では public ディレクトリ配下に画像などの静的アセットを配置できる。
<img src="./images/vercel.svg alt="vercel" />
また、Next.js では、HTML の <img>
要素を拡張した next/image
を用意している。
これにより、画像サイズの最適化や Lazy Load [1] を自動で行うことができる。
import Image from 'next/image';
const YourComponent = () => (
<Image
src="/images/vercel.svg"
height={144}
width={144}
alt="vercel"
/>
);
メタデータ
React では index.html に設定した <head>
タグしか編集できないが、Next.js ではページごとに <head>
タグを編集できる。
これにより、以下の恩恵を受けることができる。
- 検索エンジンにページの内容を正しく伝えることができ、SEO 的に強くなる
-
<title>
タグの内容がページのタイトルに反映される
import Head from 'next/head'
import Link from 'next/link'
const FirstPost = () => {
return (
<>
<Head>
<title>First Post</title>
</Head>
<div>First Post</div>
<Link href="/">← Back to home</Link>
</>
)
}
Third-Party製 JavaScript
- TBW
CSS Styling
- TBW
-
画像の読み込みに時間差を設けて表示させ、画面表示の高速化を図る仕組み ↩︎
Pre-rendering と Data Fetching
概要
- 2つの Pre-rendering 手法
Pre-rendering
- Pre-rendering:事前に HTML を生成すること(Next.js ではデフォルトで全ページを Pre-render している)
- 通常の React ではブラウザ側で HTML を組み立てる(クライアントサイドレンダリング)
- ブラウザではない場所で、事前に生成することでブラウザの負荷を減らし、表示を高速化できる
- レンダリングしたページをクローラーに見せられるため、SEO 的に強くなる
Pre-rendering | No Pre-rendering |
---|---|
2つの Pre-rendering
Next.js は Pre-rendering に2つの形式を用意している。
- Static Generation(SG):ビルド時に HTML を生成する(表示が一番早い)
- Server-Side Rendering(SSR):ユーザーのリクエストごとに HTML を生成する
Static Site Genereation | No Pre-rendering |
---|---|
※開発環境では、開発を容易にするために Static Generation でもすべてのリクエストで Pre-rendering される。本番環境では、Static Generation はビルド時に一度だけ行われ、すべてのリクエストで行われるわけではない。
Pre-rendering の使い分け
Next.js ではページごとに SG と SSR の使い分けをすることができる(基本的には SSG を推奨)。
以下のような使い分けが求められる。
- Static Site Generation:更新頻度が低いページ
- ブログ
- ECサイト
- Landing Page
- 問い合わせ
- Server-Side Rendering:更新頻度が高いページ
- SNS
- チャット
Static Generation のデータの有無による違い
- 外部データなし
- ビルド時に HTML をレンダリング
- 外部データあり
- ビルド時に DB や外部 API からデータを取得
- 取得したデータを使用して HTML をレンダリング
上記のように外部データを取得したい場合は getStaticProps
という非同期関数を使用する。
export default const Home = (props) => { ... }
export const getStaticProps = async() => {
// Get external data from the file system, API, DB, etc.
const data = ...
// The value of the `props` key will be
// passed to the `Home` component
return {
props: ...
}
}
※開発環境では、各リクエストごとに実行される。
例
getStaticProps
の例として、ファイルシステムからデータを読み込んでシンプルなブログを作成する。
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
const postsDirectory = path.join(process.cwd(), 'posts')
export type PostData = {
id: string
date: string
title: string
}
export const getSortedPostsData = (): PostData[] => {
// posts 以下のファイルを取得
const fileNames = fs.readdirSync(postsDirectory)
const allPostsData = fileNames.map((fileName) => {
const id = fileName.replace(/\.md$/, '')
// マークダウンファイルを文字列として読み込む
const fullPath = path.join(postsDirectory, fileName)
const fileContents = fs.readFileSync(fullPath, 'utf8')
// メタデータ部分の解析
const matterResult = matter(fileContents)
return {
id,
...(matterResult.data as { date: string; title: string }),
}
})
// postを日付順に並べ替える
return allPostsData.sort((a, b) => {
if (a.date < b.date) {
return 1
} else {
return -1
}
})
}
ここで、getStaticProps
を使用して、getSortedPostsData
からデータを fetch する。
import { getSortedPostsData, PostData } from '../lib/posts'
type Props = {
allPostsData: PostData[]
}
export const getStaticProps = async () => {
const allPostsData = getSortedPostsData()
return {
props: {
allPostsData,
},
}
}
const Home = ({ allPostsData }: Props) => {
return (
<section>
<h2>Blog</h2>
<ul>
{allPostsData.map(({ id, date, title }) => (
<li key={id}>
{title}
<br />
{id}
<br />
{date}
</li>
))}
</ul>
</section>
)
}
export default Home
getStaticProps の詳細
前節では、ファイルシステムからデータを取得したが、外部 API からデータを取得しても問題なく動作する。
export const getSortedPostsData = async () => {
const res = await fetch('..');
return res.json();
}
また、getStaticProps
はページからしか書き出せない。
それは、ビルド時に実行することを前提としており、レンダリングされる前に必要なデータをすべて持っている必要があるため。
なので、頻繁に更新されたり、ユーザーのリクエストごとに変更されるデータは適していない。
その場合は Server-Side Rendering を使用する必要がある。
リクエスト時のデータ取得
Server-Side Rendering を使用するためには、getStaticProps
の代わりに getServerSideProps
を export する必要がある。
export const getServerSideProps = async (context) => {
return {
props: {
// props for your component
},
};
}
Dynamic Routes
外部データに依存するページの path
Next.js では、外部データに依存する path を持つページを静的に生成することができる。
実現したいこととしては前章で用いた各ファイルを表示するページ URL を以下のように動的に変化させたい。
- /posts/file1
- /posts/file2
Next.js では []
で括られたページが動的ルートとなり、そのファイルは pages/posts/[id].tsx
のように命名する。
getStaticPaths の実装
ファイルの ID を取得する関数を追加する。
ただし、返されるリストは(今回は id キーを持つ)オブジェクトの配列である必要がある。
export const getAllPostIds = () => {
const fileNames = fs.readdirSync(postsDirectory)
return fileNames.map((fileName) => {
return {
params: {
id: fileName.replace(/\.md$/, ''),
},
}
})
}
そして、getAllPostIds
のメソッドを import して getStaticPaths
の中で使用する。
import { getAllPostIds } from '../../lib/posts';
const Post = () => {
return <div>...</div>
}
export const getStaticPaths = async () => {
const paths = getAllPostIds();
return {
paths,
fallback: false,
};
}
export default Post
getStaticPropsの実装
指定された id のファイルをレンダリングするために必要なデータを fetch する。
export const getPostData = (id: string) => {
const fullPath = path.join(postsDirectory, `${id}.md`)
const fileContents = fs.readFileSync(fullPath, 'utf8')
// メタデータ部分を解析
const matterResult = matter(fileContents)
// データをidで結合
return {
id,
...matterResult.data,
}
}
そして、getPostData
のメソッドを import して getStaticProps
の中で使用する。
import type { GetStaticProps } from 'next'
export const getStaticProps: GetStaticProps = async ({ params }) => {
const postData = getPostData(params?.id as string)
return {
props: {
postData,
},
}
}