AsaDesign

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で利用する関数なども、pagespages/apiではなく、libディレクトリを作成して配置しましょう。
  • アイコン・ロゴ・Faviconなどの静的ファイルを追加したい場合、publicディレクトリ以下に配置しましょう。そうすることで、<img src="/logo.svg"/>のように/から始まる相対パスでファイルを指定できます。

現時点での表示結果

% npm run dev
pages/index.tsxが表示されている

(2) 静的サイトを作る

pagesディレクトリでページの追加ができる

  • pages/index.js(tsx)からImage ライブラリの削除:サーバー側で画像の最適化を行う機能なので、静的サイトでは使えない ※どこでエラーが出るか見たいので消さないでおきます
  • pagesディレクトリに新しいページを作る:pages/hello-world.jsx(tsx)という名前のファイルを作ります。
export default function HelloWorld() {
  return (
    <div>
      <h1>Hello World🙌</h1>
    </div>
  )
}
pages/hello-world.tsx

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する設定を書くのは効率的ではありません。

全てのページで利用するコンポーネントやライブラリは、pages/_app.jsにまとめて実装できます。

Zenn

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} />を必ず含める必要があることに注意しましょう。

このコンポーネントを消すと、pages/内の各ファイルで指定したページの内容が表示されなくなります。

Zenn

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ライブラリの読み込みや、bodyタグにクラス名・属性を追加したい場合には、pages/_app.jsの代わりにpages/_documents.jsxを利用します。

Zenn
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の内容が変わっていることが確認できました。