最近 Next.js Learn を使って Next.js に入門した.今度は Next.js でプロトタイプを実装しながら学ぶため,プロジェクトの初期構築として create-next-app を使うことにした.少し調べたところ,create-next-app はもともと Segment 社で実装されて,現在は Next.js を管理する ZEIT 社に譲渡されている.さらに2019年10月に再実装された create-next-app もリリースされている.
with-typescript
テンプレート
create-next-app にテンプレートを指定するオプションがある.試しに Next.js と TypeScript 組み合わせた with-typescript
テンプレートを指定したところ,Next.js を学ぶのに便利な構成になっていた.Next.js 9.3 にも対応している.今回は create-next-app の with-typescript
テンプレートを使って学べる「計6点」を紹介する.コードを書きながら試行錯誤するなら Next.js Learn よりも使える.
- ディレクトリ構造
Link
コンポーネントLayout
コンポーネントgetStaticProps
関数getStaticPaths
関数と Dynamic Routes 機能- 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
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 List や Users 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 をクリックすると,一覧画面に遷移する.
一覧画面を実装した pages/users/index.tsx
を読むと,Next.js 9.3 でリリースされた新機能 getStaticProps
関数を使って実装されている.getStaticProps
関数を使うと,Next.js のビルド時に静的ファイルを生成できるため,不要な SSR (Server-Side Rendering) を減らせる.今回は API ではなく,ファイルデータ utils/sample-data.ts
を読み込む実装になっている.当然ながら,全ての一覧画面を静的ファイルにするべきという意味ではなく,あくまでサンプルとして.
なお,Alice や Bob など,一覧部分は,独自実装の 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 など,特定のユーザーをクリックすると,詳細画面に遷移する.
詳細画面は 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-app で with-typescript テンプレートから Next.js プロジェクトを初期構成すると,Next.js の主要な機能を学べる.一通りの機能を試せる点や Next.js 9.3 をサポートしている点など,Next.js Learn よりも便利だなと感じた.機能ごとに学びたかったら Next.js Learn を使って,コードを書きながら試行錯誤したかったら create-next-app で新規プロジェクトを作ってしまうと良さそう.Next.js Learn の紹介記事も載せておく!