Next.js 13
Next.js 13を使ってみます。
インストール
npx create-next-app
でインタラクティブにインストールできるが、コマンド一発でインストールで良い。
npx create-next-app sample --ts --eslint --experimental-app --src-dir --use-npm
typescriptでeslint、appディレクトリ、srcディレクトリを作成して、npmを利用します。
create-next-app --help
npm run dev
で起動してhttp://localhost:3000を確認
ディレクトリ確認
.next、node_modules除く
sample
│ .eslintrc.json
│ .gitignore
│ next-env.d.ts
│ next.config.js
│ package-lock.json
│ package.json
│ README.md
│ tsconfig.json
│
├─.vscode
│ settings.json
│
├─public
│ favicon.ico
│ next.svg
│ thirteen.svg
│ vercel.svg
│
└─src
├─app
│ globals.css
│ head.tsx
│ layout.tsx
│ page.module.css
│ page.tsx
│
└─pages
└─api
hello.ts
srcディレクトリを作成するように指定したのでsrcディレクトリあり。
appディレクトリを作成するように指定したのでappディレクトリにトップページで表示されるファイルがある。
appディレクトリはNext.js 13から追加された機能でpagesディレクトリとは異なります。
段階的な移行ができるようにこれまでのディレクトリ構造と併用できるようですが、同じURLパスを意味する設定をした場合conflictが起きます。
新規でNextjs 13で作るときは、pagesディレクトリを削除してもかまいません。pages/api/hello.tsがありますが、ここはreact pageの代わりにapi rootとして使われます。
Nuxt.js 3のserverみたいにモックAPIとかで利用すると便利そうです。
appディレクトリ
このディレクトリによりルーティングが変わります。
/app/about/page.js
のファイルが、URL /aboutで使われるファイルになります。page.jsをindex.htmlと思うとスッキリすると思います。個人的にはindex.jsが良かったんだけど、page.jsに変わってしまいました。
/appディレクトリ内のコンポーネントは、デフォルトでServer Componentとしてあつかわれます。Client Componentとして扱うときは、ファイルの先頭で"use client"
と宣言します。
「getStaticProps
や getServerSideProps
は使えません。」デフォルトServer Componentなので必要ないというのが正しいです。async/await、Fetch APIを利用してキャッシュを有効利用しパフォーマンスを向上させます。
エラー対応
- next.config.jsでCannot find module 'next/babel' が表示された
next/babelを.eslintrc.jsonに追加する
{
"extends": ["next/core-web-vitals", "next/babel"]
}
extends は既存の設定を流用して新しい設定を作り出す方法(ルールを拡張できる機能)です。
開発
共通コンポーネント(Header)を追加
アプリケーション共通でグローバルナビゲーションを追加したい場合があるでしょう。Headerコンポーネントを作成してlayoutファイルでimportします。
ディレクトリのきりかたは・・・バチっとだれか決めてほしいなぁ。自分が知らないだけか?とりあえず、commonってディレクトリを作成し、header.tsxを配置します。
import Link from 'next/link';
export const Header = () => {
return (
<header>
<nav>
<ul>
<li>
<Link href="/">Home</Link>
</li>
<li>
<Link href="/about">About</Link>
</li>
</ul>
</nav>
</header>
);
};
Linkコンポーネントは、Next 12まではaタグをネストしなければならなかったが、13からはシンプルに記載できる。
Layoutファイルでimportします。
import './globals.css'
import { Header } from './common/header';
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="ja">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>
<Header />
{children}
</body>
</html>
)
}
ディレクトリごとにレイアウト追加
ディレクトリ下にLayoutファイルを作成することでディレクトリ以下にのみ適用されるレイアウトを設定することができます。
具体的には、必要になったときに追記します。
ざっくりと説明するとRootLayoutのchildrenの中に共通html(レイアウト)を設定。そのレイアウトからchirdrenでpageコンポーネントが呼ばれる。
fetch関数
fetch関数を試すためにjsonを用意します。せっかくなのでここでは、pages/api/ に簡単なjsonをレスポンスするファイルを作成します。
pages/api/list.ts
import type { NextApiRequest, NextApiResponse } from 'next'
export default function handler(req: NextApiRequest, res: NextApiResponse) {
res.status(200).json(
[
{ id: 1,
title: 'nuxt.jsとは',
summary: 'nuxt.jsのサマリー',
contents: 'nuxt.jsについての全文',
created_by: 'hoge',
created_at: '2022-02-01 12:00:00.000'
},
{ id: 2,
title: 'next.jsとは',
summary: 'next.jsのサマリー',
contents: 'next.jsについての全文',
created_by: 'hogehoge',
created_at: '2022-02-01 13:00:00.000'
},
{ id: 3,
title: 'nuxt.jsとnext.jsの違い',
summary: 'nuxt.jsとnext.jsの違いのサマリー',
contents: 'nuxt.jsとnext.jsの違いの全文',
created_by: 'moge',
created_at: '2022-02-02 12:00:00.000'
},
]
)
};
このjsonをfetchを使って取得
type Article = {
id: number;
title: string;
summary: string;
contents: string;
created_by: string;
created_at: string;
};
const getArticles = async (): Promise<Article[]> => {
const response = await fetch('http://localhost:3000/api/list');
const articles: Article[] = await response.json();
return articles;
};
export const Articles = async (): Promise<JSX.Element> => {
const articles = await getArticles();
return (
<>
<h2>記事一覧</h2>
<table>
<thead>
<tr><th>ID</th><th>title</th><th>summary</th><th>created by</th><th>created at</th></tr>
</thead>
<tbody>
{articles && articles.map((article) => <tr key={article.id}>
<td>{article.id}</td>
<td>{article.title}</td>
<td>{article.summary}</td>
<td>{article.created_by}</td>
<td>{article.created_at}</td>
</tr>)}
</tbody>
</table>
</>
);
};
page.tsx で呼ぶ
Next13でasyncを使った非同期サーバーコンポーネントを使っていると、typescriptの警告が出ます。
今のところ解決策は、{/* @ts-expect-error Server Component */}
を書く。
import { Articles } from '@/app/about/articles'
const About = () => {
return (
<div>
{/* @ts-expect-error Server Component */}
<Articles />
</div>
);
};
export default About;
SEOを気にするプロジェクトとかであれば、ホントにサーバー側で処理されてるかが重要だったりします。そんなときは、curlコマンドでソースを確認します。
(javascriptを実行させなければ何でもいいですが、curlが楽ちんで確実です。)
client component
当然client側で行わなければならない処理は存在します。
clickイベントやuseState、useEffectやブラウザの機能を利用する場合はClient Componentを利用します。
よくあるカウントアッププログラムで確認します。
import { useState } from 'react';
export const Counter = () => {
const [count, setCount] = useState<number>(0);
const countUp = () => {
setCount((i) => ++i);
};
return (
<>
<h2>カウンター</h2>
<div>Count: {count}</div>
<button onClick={countUp}>+</button>
</>
);
};
このコンポーネントを実装するとエラーが出ます。
Maybe one of these should be marked as a client entry with "use client"
指摘されている通り、ファイルの先頭に'use client';
を宣言します。
念のためcurlでhtmlを確認します。
<h2>カウンター</h2><div>Count: <!-- -->0</div><button>+</button>
デフォルトの値が表示されて、HTMLのタグもキチンと表示されています。
Suspense
Nuxt.js 3のuseLazyFetchと同じようなものです。
誤解を恐れずに言うなら、ページ内容をバッファするかどうかです。
php.iniのoutput_buffering = 0 に設定したり、昔のプログラムを経験したことがある人ならClassic ASPでResponse.Buffer = FALSE
なんて設定をした人がいるかもしれない。
バッファさせたほうが処理は早くなるのだけど、レスポンスタイム(画面の描画が始まるの)が遅くなる。
バッファしなければとりあえず、時間のかかっているところより上は表示できる。
こうすることで、ユーザーにはローディングしているから時間がかかっているんだなと分からせることができ、何も表示されない時間を短くすることができます。
時間のかかるコンポーネントの親に設定してあげます。
import { Articles } from '@/app/about/articles'
import { Suspense } from 'react';
const About = () => {
return (
<div>
<Suspense fallback={<p>Loading...</p>}>
{/* @ts-expect-error Server Component */}
<Articles />
</Suspense>
</div>
);
};
export default About;
エラー対応
- Hydration failed
これはよくでます。Nuxt.jsでもでます。ほとんどの原因がHtmlのタグの書き方がおかしい。
<p>のなかに<div>があったり、<table>タグに<thead><tbody>がなかったり。
サーバー側でhtmlを上記のようにレンダリングしてるんだけど、クライアント側(react)でレンダリングすると異なるhtmlになってるというのがこのエラー。
- Next13でasyncを使った非同期サーバーコンポーネントを使っていると、typescriptの警告が出る
今のところ解決策は、{/* @ts-expect-error Server Component */}
を書く。
tsconfig.json
エイリアスの設定
paths オプションを使用することで、モジュールを読み込むパスのエイリアスも設定できます。
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"@/*": ["./*"]
}
}
}
importで、@を利用すると、./src/ からのパスになります。と言う意味。
場合によっては、appまでここに含めてもいいかもしれない。
※ このファイルを変更してもvs codeには反映されないという事象が起きた。vs codeを再起動すると問題は解消された。
コマンド
nextのコマンド
コマンド | 説明 |
---|---|
next dev | 開発サーバーを起動します。このコマンドで起動したサーバーはソースに変更があった場合は即座に反映される。(ホットリロード) |
next build | 本番用にアプリケーションをビルドします。これによりHTML, CSS, JSが最適化された形で、.nextフォルダに出力される。 |
next start | Next.js の本番サーバーを起動します。(Node.jsサーバーを起動します) |
next lint | Next.js に組み込まれた ESLint の設定をします。 |
備忘録
基本的なこともあれなんだっけ?っとなったときのために思い付きでメモを残す。
export defaultとは?
変数や定数、関数などの処理を1つの機能として渡します。
importするときに指定するのもモジュール名の1つだけです。
と書いてみたが、自分の中でしっくりくるのは、「default exportはnamed exportでもあり、defaultという名前のエクスポート」
これであれば、1つだけしか書けない理由も理解できるし、defaultってなんか特別なんだっけ?とならない。
Linkコンポーネント
replace オプションを指定すると、ページ遷移前の URL がブラウザの履歴に残りません(戻るキーで戻らなくなります)。
<Link href="/about" replace>About</Link>
外部リンクは・・・aタグを利用するままのようです。こちらはNuxt.js 3に軍配か?好みの問題かな。
React フラグメントとは?
React コンポーネントはルートの要素がひとつでないといけないという制限があるため、 React フラグメントを用いてまとめることが可能です。
export const Hoge = () => {
return (
<>
<p>ひとつめ</p>
<p>ふたつめ</p>
</>
);
};
でも、こちらのほうが、レンダリングに時間がかかるらしいので、divタグを使うのが無難かも・・・。
.ts と .tsx の違い
.tsはtypescriptファイル。.tsxはJSXを含むtypescriptファイル。全部、.tsxとしてもいいが、viewのファイルなのかロジックが書かれるファイルなのか拡張子で判別できるように使い分けるのが良い。
npxコマンド
npx tsc
トランスパイル
package.jsonのprivateってなに?
ライブラリを作る人はGitHubにコミットして、ライブラリを公開する時にnpmにリリース。
ライブラリを使う人はnpmからインストール。
このライブラリをnpmに間違ってリリースしないように、package.jsonに "private": true,
と定義します。
TL;DR ってなに?
よく見るTL;DRは、「too long; didn't read 長すぎて読まなかった」って意味です。