Next.js 14.2へバージョンアップした際に発生したビルドエラーの解消

2024/05/18に公開

前提

お仕事でNext.jsを使用したプロダクトを開発しています。

社内で色々とごたついていたので、バージョンアップが遅れてしまっていましたが、ようやく実施してみるとビルドエラーが発生するようになってしまったので、それらを解消した際の備忘録になります。

今回行ったNext.jsのバージョンアップは14.0.3 -> 14.2.3となり、app routerとTailwind CSSを使用しています。

TL;DR

  • SCコンポーネントのexportと、CCであるべきコンポーネントのexportを同じindex.tsに書かない
  • Tailwind CSSのimportはRoot Layoutのみで行う

発生したエラー

usePathnameを使用したコンポーネントがClient Componentであるべきと怒られる

エラー内容

./src/components/Parent/Child/Child.tsx
Error: 
  x You're importing a component that needs usePathname. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
  | Learn more: https://nextjs.org/docs/getting-started/react-essentials
  | 
  | 
   ,-[my-app/src/components/Parent/Child/Child.tsx:1:1]
 1 | import { usePathname } from "next/navigation";
   :          ^^^^^^^^^^^
 2 | // import { useState } from "react";
 3 | 
 4 | export function Child() {
   `----

Import trace for requested module:
./src/components/Parent/Child/Child.tsx
./src/components/Parent/Child/index.ts
./src/components/Parent/index.ts
./src/app/page.tsx

app router導入時にありがちな、CCでしか使えないよ、というエラーです。

ディレクトリ構成

src/
├── app
│   └── page.tsx
└── components
    └── Parent ("use client"を宣言)
        ├── Child
        │   ├── Child.tsx (usePathnameを使用)
        │   └── index.ts
        ├── Parent.tsx
        └── index.ts

コンポーネントを呼び出している順序としては

page.tsx -> Parent.tsx -> Child.tsx

のようになっており、usePathnameはChild.tsxで使用しています。

Parent.tsxではuse clientを宣言しているため、ここがSCとCCの境界になっています。

つまり、usePathnameを使用しているChild.tsxは、その親であるParent.tsxにてCCの内側に位置していることになります。

原因

基本的に、use clientを宣言しているコンポーネントから呼び出すコンポーネントは、その中で改めてuse clientを宣言しなくてもCCで扱われます。

実際、バージョンを上げる前では、そのように認識されていたため、エラーが生じていなかったと思われます。

ここで、エラーログを確認するとインポートのトレース情報が出力されています。

Import trace for requested module:
./src/components/Parent/Child/Child.tsx
./src/components/Parent/Child/index.ts
./src/components/Parent/index.ts
./src/app/page.tsx

上記を下の行からみると、下から2行目でParent.tsxのexport文を書いているindex.tsの次に、Child.tsxが参照されてしまっているようです。

すなわち、SCとCCの境界であるParent.tsxを介さずに、直接Child.tsxが参照されてしまってるようです。

このような参照になってしまった原因は、./src/components/Parent/index.tsに、Child.tsxのexport文も併せて書いてしまっているためでした。

./src/components/Parent/index.ts
export { Parent } from "./Parent";
export { Child } from "./Child";

解消方法

本来、Child.tsxはParent.tsxから参照するコンポーネントなので、上記のindex.tsでのexportは不要となるため、exoport文を削除したところ、ビルドが成功しました。

./src/components/Parent/index.ts
export { Parent } from "./Parent";
- export { Child } from "./Child";
余談

このエラーは14.1では確認できず、14.2で生じるようになっていますが、この差分が何に起因しているものなのかは追えていないです。

しかし、ビルドが成功する14.1にて、usePathnameではなくuseStateを使用した場合は、14.2と同じくエラーとなります。

このことから、下記のいずれかの要因で生じるようになったのではないかと推察しましたが、憶測の域を出ないです。
(このあたりご存知の方がいらっしゃれば教えていただきたいです。)

  • 14.2での最適化の中で、importを参照する処理が変わった。
  • 14.1までの時点では見逃されていたusePathnameがCCであるべきチェックが、14.2で正しく行われるようになった

Tailwind CSSに対してSyntaxErrorが発生する

エラー内容

/node_modules/.pnpm/tailwindcss@3.3.3/node_modules/tailwindcss/tailwind.css:1
@tailwind base;
^

SyntaxError: Invalid or unexpected token
    at internalCompileFunction (node:internal/vm:73:18)
    (以下略)

Error occurred prerendering page "hogehoge". Read more: https://nextjs.org/docs/messages/prerender-error

原因と解消方法

14.2のアップデート内容として、CSSの最適化が挙げられます。

CSSをチャンク化することで、実現しているそうですが、このチャンク化においては、CSSを読み込む順序が重要そうです。

今回、エラーが起きている箇所を限定していったところ、重複してTailwindをインポートしている箇所があることがわかりました。

src/
└── app
    ├── global.css
    ├── layout.tsx(Tailwindをインポート)
    └── hoge
        └── layout.tsx(Tailwindをインポート)
src/app/hoge/layout.tsx
import "tailwindcss/tailwind.css";
(以下略)

Next.jsのドキュメントにも記載されている通り、Tailwindを使用する場合はRoot Layoutでの読み込みが推奨されていたため、該当のインポート文を削除したところエラーが解消されました。

GitHubで編集を提案

Discussion