Next.jsのpre-rendering

reactnext.jstypescriptweb

Next.jsはwebpackやbabel,ESLintを内包し、 SSG/SSRやリソースの最適化といった機能を提供するReactのフレームワーク。

create-next-appを実行し、npm run dev すると development modeでサーバーが起動するので localhost:3000/abcd にアクセスすると pages/abcd.tsxexport default されたコンポーネントが描画される。 Fast Refleshが効いているのでファイルを更新するとすぐに反映される。

$ npx create-next-app --ts
$ cd my-app
$ tree -I node_modules .
.
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── pages
│   ├── _app.tsx
│   ├── api
│   │   └── hello.ts
│   └── index.tsx
├── public
│   ├── favicon.ico
│   └── vercel.svg
├── styles
│   ├── Home.module.css
│   └── globals.css
└── tsconfig.jso

$ npm run dev

# prod
$ npm run build
$ npm run start

pre-rendering

フロントエンドでDOMをレンダリングする場合、通常はJSのロード後コンポーネントが初期化されるhydrationを待つ必要があるが、 Next.jsでは全てのページに対して、ビルド時のStatic Generation (SSG)か、リクエスト時のServer Side Redering (SSR)を行いpre-renderingするため、最初からページを表示することができる。SEOにも有効。

デフォルトではStatic Generationされ、SSRよりもこちらが推奨される。 /pages/1 のようなDynamic Routesでも、 ブログの記事のように動的なコンテンツでない場合、 事前に getStaticPaths() でpathと、それに対応するpropsを getStaticProps() で取得してStatic Generationできる。

$ cat pages/\[id\].tsx 
import styles from '../styles/Home.module.css'
import { useRouter } from 'next/router'
import Link from 'next/link'
import { GetStaticPropsContext, GetStaticPaths, InferGetStaticPropsType } from 'next'

type Params = {
	id: string
}

export const getStaticPaths: GetStaticPaths<Params> = async () => {
  return {paths: [{params: {id: "1"}}, {params: {id: "2"}}], fallback: false};
}

export const getStaticProps = async (context: GetStaticPropsContext<Params>) => {
  return {props: {contents: `id is ${context.params?.id}`, id: context.params?.id}}
}

export default function Home({ contents, id }: InferGetStaticPropsType<typeof getStaticProps>) {
  const router = useRouter()
  return (
    <div className={styles.container}>
      {contents}
      <Link href={`/${parseInt(id || "0") + 1}`}>
        <a>+1</a>
      </Link>
    </div>
  )
}

$ curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/1 
200
$ curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/100
404

リクエストごとにSSRする場合、 getServerSideProps() でpropsを返す。

import styles from '../styles/Home.module.css'
import { useRouter } from 'next/router'
import Link from 'next/link'
import { GetServerSideProps, GetServerSidePropsContext, InferGetServerSidePropsType } from 'next'

type Params = {
	id: string
}

export const getServerSideProps = async (context: GetServerSidePropsContext<Params>) => {
  return {props: {contents: `id is ${context.params?.id}`, id: context.params?.id}}
}

export default function Home({ contents, id }: InferGetServerSidePropsType<typeof getServerSideProps>) {
  const router = useRouter()
  return (
    <div className={styles.container}>
      {contents}
      <Link href={`/${parseInt(id || "0") + 1}`}>
        <a>+1</a>
      </Link>
    </div>
  )
}

$ curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/1 
200
$ curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/100
200

production serverでは、Staric GenerationされたページへのLinkがviewportに入った際にpropsが先読みされる

// GET http://localhost:3000/_next/data/EOKMebLh4ObHV3QqgePIS/2.json
{"pageProps":{"contents":"id is 2","id":"2"},"__N_SSG":true}