kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Next.js に入門するのに便利な create-next-app の with-typescript テンプレート

最近 Next.js Learn を使って Next.js に入門した.今度は Next.js でプロトタイプを実装しながら学ぶため,プロジェクトの初期構築として create-next-app を使うことにした.少し調べたところ,create-next-app はもともと Segment 社で実装されて,現在は Next.js を管理する ZEIT 社に譲渡されている.さらに2019年10月に再実装された create-next-app もリリースされている.

nextjs.org

with-typescript テンプレート

create-next-app にテンプレートを指定するオプションがある.試しに Next.js と TypeScript 組み合わせた with-typescript テンプレートを指定したところ,Next.js を学ぶのに便利な構成になっていた.Next.js 9.3 にも対応している.今回は create-next-appwith-typescript テンプレートを使って学べる「計6点」を紹介する.コードを書きながら試行錯誤するなら Next.js Learn よりも使える.

  1. ディレクトリ構造
  2. Link コンポーネント
  3. Layout コンポーネント
  4. getStaticProps 関数
  5. getStaticPaths 関数と Dynamic Routes 機能
  6. API Routes 機能

なお with-typescript 以外のテンプレートは以下にある.

準備

まず npx create-next-app--example オプションを使ってプロジェクトを構築する.構築後に yarn dev を実行すると,すぐに http://localhost:3000 にアクセスできるようになる.

$ npx create-next-app --example with-typescript sandbox-create-next-app-ts
$ cd sandbox-create-next-app-ts
$ yarn dev

f:id:kakku22:20200413214932p:plain

1. ディレクトリ構成

create-next-app を実行した直後は以下のようなディレクトリ構成になっている.node_modules 以外を載せた.議論の余地はあるかもしれないけど,pages/ 以外にコンポーネントを管理する components/ や,型定義を管理する interfaces/ など,最低限このあたりは分割しておこう!という指針になる.

$ tree -I node_modules
.
├── README.md
├── components
│   ├── Layout.tsx
│   ├── List.tsx
│   ├── ListDetail.tsx
│   └── ListItem.tsx
├── interfaces
│   └── index.ts
├── next-env.d.ts
├── package.json
├── pages
│   ├── about.tsx
│   ├── api
│   │   └── users
│   │       └── index.ts
│   ├── index.tsx
│   └── users
│       ├── [id].tsx
│       └── index.tsx
├── tsconfig.json
├── utils
│   └── sample-data.ts
└── yarn.lock

7 directories, 16 files

2. Link コンポーネント

まず,トップページの実装を確認するため pages/index.tsx を読む.すると Link コンポーネントを使って /about へのリンクが実装されている.React Router などを使わずに簡単に実装できる.

import Link from 'next/link'
import Layout from '../components/Layout'

const IndexPage = () => (
  <Layout title="Home | Next.js + TypeScript Example">
    <h1>Hello Next.js 👋</h1>
    <p>
      <Link href="/about">
        <a>About</a>
      </Link>
    </p>
  </Layout>
)

export default IndexPage

3. Layout コンポーネント

画面の1番上には Users ListUsers API など,他にもリンクが実装されている.既に載せた pages/index.tsx を読むと,独自実装の Layout コンポーネントがあり,title 属性が指定されている.コンポーネントの実装を確認するため components/Layout.tsx を読むと,画面共通のレイアウトが実装されている.ここにまた Link コンポーネントがあったり,{children} を使ってコンテンツを受け渡していたり,ベーステンプレートの実装として参考になる.

import * as React from 'react'
import Link from 'next/link'
import Head from 'next/head'

type Props = {
  title?: string
}

const Layout: React.FunctionComponent<Props> = ({
  children,
  title = 'This is the default title',
}) => (
  <div>
    <Head>
      <title>{title}</title>
      <meta charSet="utf-8" />
      <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    </Head>
    <header>
      <nav>
        <Link href="/">
          <a>Home</a>
        </Link>{' '}
        |{' '}
        <Link href="/about">
          <a>About</a>
        </Link>{' '}
        |{' '}
        <Link href="/users">
          <a>Users List</a>
        </Link>{' '}
        | <a href="/api/users">Users API</a>
      </nav>
    </header>
    {children}
    <footer>
      <hr />
      <span>I'm here to stay (Footer)</span>
    </footer>
  </div>
)

export default Layout

4. getStaticProps 関数

次にトップページから Users List をクリックすると,一覧画面に遷移する.

f:id:kakku22:20200413215057p:plain

一覧画面を実装した pages/users/index.tsx を読むと,Next.js 9.3 でリリースされた新機能 getStaticProps 関数を使って実装されている.getStaticProps 関数を使うと,Next.js のビルド時に静的ファイルを生成できるため,不要な SSR (Server-Side Rendering) を減らせる.今回は API ではなく,ファイルデータ utils/sample-data.ts を読み込む実装になっている.当然ながら,全ての一覧画面を静的ファイルにするべきという意味ではなく,あくまでサンプルとして.

なお,AliceBob など,一覧部分は,独自実装の List コンポーネント (components/List.tsx) と ListItem コンポーネント (components/ListItem.tsx) に実装されている.

export const getStaticProps: GetStaticProps = async () => {
  // Example for including static props in a Next.js function component page.
  // Don't forget to include the respective types for any props passed into
  // the component.
  const items: User[] = sampleUserData
  return { props: { items } }
}

5. getStaticPaths 関数と Dynamic Routes 機能

一覧画面で Alice など,特定のユーザーをクリックすると,詳細画面に遷移する.

f:id:kakku22:20200413215135p:plain

詳細画面は pages/users/[id].tsx に実装されている.詳細画面はユーザーごとに異なるため,Next.js の「Dynamic Routes 機能」を使って,任意の id に対応する.そのために Next.js 9.3 でリリースされた新機能 getStaticPaths 関数を使って実装されている.まず getStaticProps 関数で,URL から取得した id を使ってファイルデータを検索し,ビルド時に静的ファイルを生成する.さらに getStaticPaths 関数で,Dynamic Routes 機能に必要なパスのリストを生成する.簡単な実装ではあるけど,参考になる.

import React from 'react'
import { GetStaticProps, GetStaticPaths } from 'next'

import { User } from '../../interfaces'
import { sampleUserData } from '../../utils/sample-data'
import Layout from '../../components/Layout'
import ListDetail from '../../components/ListDetail'

type Props = {
  item?: User
  errors?: string
}

export default class StaticPropsDetail extends React.Component<Props> {
  render() {
    const { item, errors } = this.props

    if (errors) {
      return (
        <Layout title={`Error | Next.js + TypeScript Example`}>
          <p>
            <span style={{ color: 'red' }}>Error:</span> {errors}
          </p>
        </Layout>
      )
    }

    return (
      <Layout
        title={`${
          item ? item.name : 'User Detail'
        } | Next.js + TypeScript Example`}
      >
        {item && <ListDetail item={item} />}
      </Layout>
    )
  }
}

export const getStaticPaths: GetStaticPaths = async () => {
  // Get the paths we want to pre-render based on users
  const paths = sampleUserData.map(user => ({
    params: { id: user.id.toString() },
  }))

  // We'll pre-render only these paths at build time.
  // { fallback: false } means other routes should 404.
  return { paths, fallback: false }
}

// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
export const getStaticProps: GetStaticProps = async ({ params }) => {
  try {
    const id = params?.id
    const item = sampleUserData.find(data => data.id === Number(id))
    // By returning { props: item }, the StaticPropsDetail component
    // will receive `item` as a prop at build time
    return { props: { item } }
  } catch (err) {
    return { props: { errors: err.message } }
  }
}

6. API Routes 機能

最後は Next.js で API を実装する「API Routes 機能」で,pages/api/users/index.ts のように pages/api 配下にファイルを置く.すると http://localhost:3000/api/users で API にアクセスできるようになる.

$ curl -s http://localhost:3000/api/users | jq .
[
  {
    "id": 101,
    "name": "Alice"
  },
  {
    "id": 102,
    "name": "Bob"
  },
  {
    "id": 103,
    "name": "Caroline"
  },
  {
    "id": 104,
    "name": "Dave"
  }
]

今回はファイルデータ utils/sample-data.ts をそのまま JSON で返すだけになっているけど,例えば NextApiRequest で HTTP メソッドを判定したり,API Middlewares を使って拡張もできる.最低限の API 実装という観点で参考になる.

import { NextApiRequest, NextApiResponse } from 'next'
import { sampleUserData } from '../../../utils/sample-data'

export default (_: NextApiRequest, res: NextApiResponse) => {
  try {
    if (!Array.isArray(sampleUserData)) {
      throw new Error('Cannot find user data')
    }

    res.status(200).json(sampleUserData)
  } catch (err) {
    res.status(500).json({ statusCode: 500, message: err.message })
  }
}

まとめ

create-next-appwith-typescript テンプレートから Next.js プロジェクトを初期構成すると,Next.js の主要な機能を学べる.一通りの機能を試せる点や Next.js 9.3 をサポートしている点など,Next.js Learn よりも便利だなと感じた.機能ごとに学びたかったら Next.js Learn を使って,コードを書きながら試行錯誤したかったら create-next-app で新規プロジェクトを作ってしまうと良さそう.Next.js Learn の紹介記事も載せておく!

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com