Next.jsワークショップやってみた①静的サイト編
Next.jsとStripeではじめるシンプルなECサイト開発ワークショップ – Zenn
今回の目標
- npx create-next-app@latest でプロジェクトを作る
- npm run dev で起動する
- pagesディレクトリでページの追加ができる
- <Head>コンポーネントで、ブラウザのタブの文言変更ができる
- pages/_app.js(tsx)で共通のレイアウト設定ができる
- npx next buildで静的サイトをビルドできる
- npx serve outでビルド結果を確認できる
(1) セットアップ
% npx create-next-app@latest
✔ What is your project named? … nextjs-stripe-ec
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
デフォルト設定との違い
- お手本ではPage Routerを使用しているためApp Routerは使用しない
- srcディレクトリも使用しない
% tree -I node_modules
.
└── nextjs-stripe-ec
├── README.md
├── next-env.d.ts
├── next.config.mjs
├── package-lock.json
├── package.json
├── pages
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── api
│ │ └── hello.ts
│ ├── fonts
│ │ ├── GeistMonoVF.woff
│ │ └── GeistVF.woff
│ └── index.tsx
├── public
│ └── favicon.ico
├── styles
│ ├── Home.module.css
│ └── globals.css
└── tsconfig.json
ファイルの置き場所
Zennさんより引用👇
- ページではないReactコンポーネントを追加する場合は、
components
ディレクトリを新しく作成し、そこに配置しましょう。 - 同様に、APIで利用する関数なども、
pages
やpages/api
ではなく、lib
ディレクトリを作成して配置しましょう。 - アイコン・ロゴ・Faviconなどの静的ファイルを追加したい場合、
public
ディレクトリ以下に配置しましょう。そうすることで、<img src="/logo.svg"/>
のように/
から始まる相対パスでファイルを指定できます。
現時点での表示結果
% npm run dev
(2) 静的サイトを作る
pagesディレクトリでページの追加ができる
- pages/index.js(tsx)からImage ライブラリの削除:サーバー側で画像の最適化を行う機能なので、静的サイトでは使えない ※どこでエラーが出るか見たいので消さないでおきます
- pagesディレクトリに新しいページを作る:pages/hello-world.jsx(tsx)という名前のファイルを作ります。
export default function HelloWorld() {
return (
<div>
<h1>Hello World🙌</h1>
</div>
)
}
Headコンポーネントで、ブラウザのタブの文言変更ができる
import Head from 'next/head';
...
<Head>
<title>Hello World</title>
<meta name="description" content="検索エンジン用の説明文"/>
</Head>
pages/_app.js(tsx)で共通のレイアウト設定ができる
TailwindやBootstrapなどのUIフレームワークを利用したい場合や、Stripe・Zendeskなどのライブラリを複数のページで利用したい場合、ページ毎にimportする設定を書くのは効率的ではありません。
全てのページで利用するコンポーネントやライブラリは、
Zennpages/_app.js
にまとめて実装できます。
Bootstrap version 5.xをインストールします。
% npm install react-bootstrap@2 bootstrap@5
added 20 packages, and audited 346 packages in 4s
pages/_app.js(tsx)を編集します。
before
import "@/styles/globals.css";
import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
after:ヘッダー部分を追加しました。<nav.navbar><div.container><a.navbar-brand>というふうに変換されるようです。
【エラー小話】スペルミスで時間を無駄にしてしまいました😢
Module not found: Can’t resolve ‘bootstrap/dist/css/bootstrap.min/css’ ←min.cssが正しい
import "@/styles/globals.css";
import 'bootstrap/dist/css/bootstrap.min.css';
import { Navbar, Container} from 'react-bootstrap';
function MyApp({Component, pageProps}) {
return (
<>
<Navbar>
<Container>
<Navbar bg="dark" variant="dark">
Hello EC
</Navbar.Brand>
</Container>
</Navbar>
<Component {...pageProps}></Component>
</>
)
}
export default MyApp
pages/_app.js
の注意点
pages/_app.js
では、<Component {...pageProps} />
を必ず含める必要があることに注意しましょう。このコンポーネントを消すと、
Zennpages/
内の各ファイルで指定したページの内容が表示されなくなります。
npx next buildでビルドする
ここまで作った静的サイトを、NetlifyやAWS Amplifyで使用可能なファイルにします。
【エラー小話】Type error: Binding element ‘Component’ implicitly has an ‘any’ type.
型指定してよ!と怒られた話はこちら。
% npx next build
...
Type error: Binding element 'Component' implicitly has an 'any' type.
> 5 | function MyApp({Component, pageProps}) {
| ^
公式サイトを見るとAppPropsという型定義をしていたので、真似しました。
import { AppProps } from "next/app";
...
function MyApp({Component, pageProps}: AppProps) { ← : AppPropsを追加
以上です。元のお話へどうぞ。
% npx next build
...
✓ Finalizing page optimization
Route (pages) Size First Load JS
┌ ○ / 4.88 kB 93.2 kB
├ └ css/46cbeeb393582c7c.css 1.18 kB
├ /_app 0 B 88.3 kB
├ ○ /404 180 B 88.5 kB
├ ƒ /api/hello 0 B 88.3 kB
└ ○ /hello-world 421 B 88.7 kB
+ First Load JS shared by all 119 kB
├ chunks/framework-64ad27b21261a9ce.js 44.8 kB
├ chunks/main-f61b188e1c4e2fda.js 32.1 kB
├ chunks/pages/_app-d415f9c498cd9349.js 10.5 kB
├ css/6c44dc282b4d2fb1.css 30.6 kB
└ other shared chunks (total) 818 B
○ (Static) prerendered as static content
ƒ (Dynamic) server-rendered on demand
ファイルを生成するコマンド「npx next export」は廃止されたようです。静的エクスポート – Next.js
❌`next export` has been removed in favor of ‘output: export’ in next.config.js.
=「next.config.js の「next export」は削除され、代わりに「output:export」が使用されます。」
公式サイトに従い、next.config.mjsに設定を追加し、npx next buildコマンドを実行しました。
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
output: 'export', ←追記
};
export default nextConfig;
「out」というフォルダが追加されました。
npx serve outでビルド結果を確認する
% npx serve out
Next.jsで静的ファイルを出力するには,
雰囲気データサイエンティストの備忘録next.config.js
を以下のように書き換える必要があります。output: 'export'
だけでいけそうな感もありますが,自分の場合はこれだけだとエラーが出てしまい画像ファイルの最適化をオフにする必要がありました。
images: { unoptimized: true, } を追記する必要がありました。
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
output: 'export',
images: {
unoptimized: true,
},
};
export default nextConfig;
まとめ
- npx create-next-app@latest でプロジェクトを作る
- npm run dev で起動する
- pagesディレクトリでページの追加ができる
- <Head>コンポーネントで、ブラウザのタブの文言変更ができる
- pages/_app.js(tsx)で共通のレイアウト設定ができる
- npx next buildで静的サイトをビルドできる
- npx serve outでビルド結果を確認できる
ディレクトリ構成の変化としては以下になります。
- ビルド時にoutディレクトリが生成された
- 新たに作ったhello-world.tsxが追加された
.
├── README.md
├── next-env.d.ts
├── next.config.mjs
├── out ← ビルド時にファイル群が生成された
├── package-lock.json
├── package.json
├── pages
│ ├── _app.tsx ← 共通のメインナビゲーション設置
│ ├── _document.tsx ← おそらくメインの<Html>,<Head>,<body>
│ ├── api
│ │ └── hello.ts
│ ├── fonts
│ │ ├── GeistMonoVF.woff
│ │ └── GeistVF.woff
│ ├── hello-world.tsx ← ページ単体の<Head>コンポーネントに<title>や<meta>を設置
│ └── index.tsx
├── public
│ └── favicon.ico
├── styles
│ ├── Home.module.css
│ └── globals.css
└── tsconfig.json
おまけ
bodyやhtmlの編集はpages/_documents.jsxを利用する
Google Analyticsなどの外部JSライブラリの読み込みや、
Zennbody
タグにクラス名・属性を追加したい場合には、pages/_app.js
の代わりにpages/_documents.jsx
を利用します。
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="ja"> ← enからjaに変更
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
htmlの内容が変わっていることが確認できました。