この記事はNext.js Advent Calendar 2020の18日目の記事です。
はじめに
タイトル通りです。
業務でNext.jsを約1年ぐらい触ってきて、毎回設定する項目についてまとめました。
いわゆるNext.js文脈ではなくReact, Web技術界隈のプラクティスも一部含まれますが優しい気持ちで御覧ください。
0. TypeScriptを使う
使いましょう。
TypeScriptを使う際の公式ドキュメントも充実しています。
公式ドキュメント通りにやるとtsconfig.json
がstrict: false
になっているので、strict: true
にするのをお忘れなく。
1. パスをまとめる: パスの中身を楽に書き換えられるように
※Next 10系からはas
が自動的に解釈されるようになったので、よしなに読み飛ばしてください。
https://nextjs.org/blog/next-10#automatic-resolving-of-href
よくNextのドキュメントで以下のような記述をみますが、一般的にパスを直書きするとのちのち辛い思いをすることは明らかです。
<Link href="/categories/[slug]" as="/categories/books">
雑なサンプルですが以下のようhref
, as
を返す関数を実装します。
export type LinkPropsLike = {
href: string;
as: string;
};
export const pathBuilder = {
posts: () => ({
index: (): LinkPropsLike => ({
href: '/posts',
as: `/posts`,
}),
detail: (postId: number): LinkPropsLike => ({
href: '/posts/[postId]',
as: `/posts/${postId}`,
}),
}),
};
使用例としては以下のようになります。
<Link {...pathBuilder.posts().detail(1)}>最初の記事</Link>
ちなみにそれをよしなにやってくれるライブラリもあります。下手に作るよりライブラリ等に則った方が秩序が保たれるので良いかもしれません。
https://github.com/yarnaimo/next-typed-path
GitHubのサンプルコードママ
import { $dynamic, $route, createRoutes } from 'next-typed-path'
const routes = createRoutes({
about: $route,
users: {
index: $route,
[$dynamic]: {
index: $route,
posts: {
[$dynamic]: {
index: $route,
},
},
settings: {
index: $route,
lang: $route,
},
},
},
})
routes.about // => '/about'
routes.users.index // => '/users'
routes.users('123').index // => '/users/123'
routes.users('123').posts('456').index // => '/users/123/posts/456'
routes.users('123').settings.index // => '/users/123/settings'
routes.users('123').settings.lang // => '/users/123/settings/lang'
クールですね。
TypeScript 4.1のTemplate Literal Typesがアツいので、そのうちTSだけでよしなにできるようになるかもですね。
Next.jsでそのうちサポートしてくれると非常嬉しい。
2. Vercelを使う: ホスティングやCDの設定, PRでの確認を楽に
複雑なことをやらないのであればたいていのパターンで脳死でデプロイできます。
ポチポチでSSL暗号化からCDのインテグレーションの設定までできるので重宝しています。
Vercelの紹介は別の記事や公式に任せるとして、個人的に特にアツいのはPreview deployments
です。
Vercelは以下のような開発フローを推奨しています。
- Pull Requestでいわゆるステージング環境を確認する
- マージするとProductionに反映される
後述しますが、NetlifyでPull RequestのブランチでStorybookデプロイするようにしておくとレビュー作業がはかどります。それに加えてVercelでデプロイプレビュー確認ができるとリモートにもってきて確認作業をする必要がかなり減ります。
以下のようにGitHub等のPull Request上で確認することができます。
画像は https://armno.in.th/2020/05/07/vercel/ から
※Previewで確認できるサイトのドメインはVercelがよしなに設定するため、動作にドメインの制約があるようなサービスは以上のワークフローの設定は行えません。
3. Storybookでコンポーネントを管理する: コンポーネントの確認を楽に
実装時にコンポーネントを確認しながら実装したいですよね。Storybookを入れましょう。
Storybookは以下のスクショのようにコンポーネントを確認できるツールです。
アドオンをいれることによってイベントを確認できたり、props
の中身を書き換えて確認ができたり強力なことができます。
Storybookの導入を推奨する文脈は以下のとおりです。
- コンポーネント単位での開発時に用いる
- コードレビュー時に後述のNetlifyにデプロイされたStorybookを確認する
- 共同開発者がコンポーネントを流用できるように(コミュニケーションコストを減らせる)
- 挙動の確認として
設定はわりと簡単に済みます。
Next.js ▲ + Typescript + Storybook The Really Simple Guide 2019 - DEV
https://dev.to/aprietof/next-js-typescript-storybook-the-really-simple-guide-2019-fei?signin=true
Netlifyを用いてPull RequestのブランチでStorybookをデプロイするように設定しておくとよいです。
以下のようにPull Requestで作成されたコンポーネント等をクリックするだけで確認することができます。
画像は https://www.netlify.com/blog/2016/07/20/introducing-deploy-previews-in-netlify/ から
4. APIクライアントによしなに型をつける: OASでAPIの型定義を書いて楽する
Next.jsをプロダクトで使う多くの場合、APIを参照するはずです。
APIの定義がSwaggerなどのOASを使っているプロジェクトならopenapi2aspida
やopenapi-generator
を使うことを強く推奨します。要はAPI定義ファイルから型付きのAPIクライアントを生成します。
例えば、バックエンドチームがAPIとAPI定義ファイルを提供してフロントチームがそれから実装するみたいなフローが考えられます。ツールを使わない場合はフロントの人がAPI定義ファイルからAPIクライアントのレスポンスの型をよしなにTypeScriptで記述する必要があります。(つらい)
ところがopenapi2aspida
を導入すると楽になります。流れとしては以下のようになります。
まず、APIの定義を書きます。今回はサンプルとして以下のAPI定義を用います。
https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v2.0/yaml/petstore.yaml
...
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: limit
in: query
description: How many items to return at one time (max 100)
required: false
type: integer
format: int32
responses:
"200":
description: A paged array of pets
headers:
x-next:
type: string
description: A link to the next page of responses
schema:
$ref: '#/definitions/Pets'
default:
description: unexpected error
schema:
$ref: '#/definitions/Error'
...
openapi2aspida
の設定ファイルを書いて
module.exports = {
input: "api",
outputEachDir: true,
openapi: { inputFile: "./petshop.yml" }
}
ビルドすると、
npx openapi2aspida
これだけで型付きのAPIクライアントが使える! うれC!
いわゆるスキーマ駆動開発が非常に効率的に行うことが可能になります。
aspida
については製作者様のQiitaの記事がわかりやすいのでご覧ください。
https://qiita.com/m_mitsuhide/items/68406158d35a14fa0aa2
5. Bundle Analyzerを導入する: ページが重くなっていないか確認できるように
Code SplittingをよしなにやってくれるのがNext.jsのかなりの旨味です。ページに必要じゃないソースとかを絡めてもってこないから軽くできるよといった機能です。
ただ、野良ライブラリを使用するとNextのCode Splittingでも重くなることが稀に生じます。
そのため、Bundle Analyzerを仕込んでおいてライブラリ導入時等に確認することが推奨されます。
※next build
時にNextが各ページのサイズを出力してくれるので、サイズ確認だけできればよいのであれば不要です。
@next/bundle-analyzer
を使用する場合は以下のように設定しています。
yarn add --dev @next/bundle-analyzer cross-env
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({
// 他の設定
})
{
"scripts": {
"analyze": "cross-env ANALYZE=true next build",
"build": "next build",
...
}
...
}
確認時は以下のような動きになります。
-
yarn build
してバンドルファイルを作成 -
yarn analyze
して解析結果をブラウザで確認する (内部の処理としては以下のよう) -
cross-env
で環境変数ANALYZE
にtrue
を入れる - 次に
next build
される際にenabled
がtrue
になる - Bundle Analyzerが仕事をする
- クライアントサイド, サーバーサイドのバンドルサイズがブラウザで確認できる
以下のように、視覚的にバンドルファイルに占めるコードを確認することができます。
以下の例ではlodash
が大きくバンドルファイルを占めていることが確認できますね。
画像は https://www.npmjs.com/package/webpack-bundle-analyzer のREADMEのものです。
6. 画像を最適化する
Next 10系以前はnext/image
が無いため、自分でよしなにする必要があります。
next-imagesをいれるのが結構楽だったりします。
画像サイズによってインライン展開させることができるのでHTTPリクエストを減らせます。
const withImages = require('next-images')
module.exports = withImages({
inlineImageLimit: 16384,
webpack(config, options) {
return config
}
})
また他には以下のような手法があります。画像はウェブの大半の通信を食うのでできることはやったほうがよいです。Lighthouse等もここらをやっているかどうかで大きく点数が変わってきます。
- 画像はCloudinaryやimgix, ImageFluxなどの画像よしな系SaaSで提供する
- Lazy Loadの設定を行う
- WebPを使ってあげる
- Nextから提供する画像はTinyPNG, SVGOMG等の圧縮の限りを尽くす
10系移行はnext/image
で良いでしょう。
ちなみにnext/image
は現時点ではStorybookは相性が悪いです。next/image
が普通に画像を渡しているわけじゃないからです。
https://github.com/vercel/next.js/issues/18393
@next/plugin-storybookがよしなに整備されることを祈る or contributeしましょう。お願いします。
その他
強いて言えば以下を考慮しています。
-
pages/*.(jsx|tsx)
には責務を持たせない - コンポーネントをNext.jsに依存させない
- CSSに(ある程度の)秩序をもたせるように頑張る (ex: Sass, CSS in JS等)
最後に
Reactに塩梅の良い秩序と平穏をもたらしてくれるNext.jsは非常にうまいこと自らの責務の中で成長していると思います。Next.jsの責務のそれを超えた今回の(設定|プラクティス)が(どこか|誰か)のプロジェクトの参考になれれば幸いです。