Next.js AppRouter の落とし穴
not-found.tsx
は内部コンポーネントからnotFound()
されたときしか呼ばれない
users/[userId]/not-found.tsx
というページを用意したとする。
users/[userId]/page.tsx
内部のコンポーネントでnotFound()
を呼べば、直近の404ページ、つまりusers/[userId]/not-found.tsx
が呼ばれる。
外部からURLを叩いて間違って存在しないページusers/[userId]/nopage
にアクセスした場合は、グローバルな404ページ/not-found.tsx
が使われるため注意。ちなみにnot-found.tsx
はエラーバウンダリの一種であり、ページとしては存在しないので、意図してURLを叩いてそのページに直接飛ぶことはできない。
error.tsx
にはカスタムエラーを渡せない
以下のようにerror.tsx
とpage.tsx
を実装したとき、error.tsx
にAPIError
が渡されることを期待する。しかし、error.tsx
はmessage
とdigest
プロパティを持ったError
インスタンスが渡される。
'use client'; // 必須
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
console.log(error instanceof APIError); // false
console.log(error.constructor.name); // Error
console.log(error.statusCode); // undefined statusCodeはAPIErrorのプロパティの1つとする
return (
<div>Something went wrong!</div>
);
}
export default function Page(){
throw new APIError('message', 400); // カスタムエラー
return null;
}
セキュリティ上の理由らしい
Next.jsのリファレンス
GitHub上の関連するディスカッション
v14.1.*の場合parallel routeで最初のlayoutが適用されない
以下のようなフォルダ構成で、パラレルルートを構成しているばあい、フォルダ先頭のhoge/layout
が適用されない。これは、v14.2.*で修正された
@hoge
- layout.tsx // これが適用されない
- page.tsx
@fuga
- layout.tsx
- page.tsx
layout.tsx
ページから離れるとき「保存されていません」というアラートをだす(離脱防止)が面倒になった
こちらの記事が詳しい。
v14.2.* だとオブジェクトのkey名に日本語の中黒(・)を使うとbuildできない
こちらの記事が詳しい
next-auth v4系がApp Routerのmiddelware環境(edge runtime)に対応していない
v5系で対応予定。つまりmiddelware上でnext-authのAPI(getServerSessionとか)を呼べないので直接cookieを取得するといった回避策が必要。
parallel route と interception routeを組み合わせたときの挙動が怪しい
v14.2.4で解消されたが、router.refrsh
とrouter.back
を組み合わせるとどちらかが上手く発火しないことがある。まだここらへんの挙動は安定していないので、本番で使うのは少し不安。
モノレポでutil関数をつくって、サーバーサイドとクライアントサイドで使おうと思った時、middlewareのedge runtimeにまで対応する必要がある
この記事が詳しい
midlewareの edge runtime ではlodash使えないかもしれない
おそらくlodashの関数によってはエラーが発生する。
- lodash.keyby
- lodash.isequal
Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtim
error.tsxで渡されるエラーメッセージは本番buildすると潰される
すべてのエラーは以下の文章に置き換わる。セキュリティのためらしい。ステータスコードによってエラーを出し分けるのはどうやったらいいのだろう?ステータスコードが200番台以外ならエラーを投げずに、画面に描画するメンタルモデルを採用すべきなのかもしれない。
An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.
おそらく関連するDiscussion
だめな書き方
export default async function Page({
params: { someId },
}: {
params: { someId: string };
}) {
// 200番台以外だとエラー、bodyが直接returnされる
const some = await fetchSomeOrThrowError(someId);
return (
<SomeDescription
some={some}
></SomeDescription>
);
}
AppRouterの想定する書き方
export default async function Page({
params: { someId },
}: {
params: { someId: string };
}) {
// { ok: boolean, body: Some }が返される
const res = await fetchSomeOrThrowError(someId);
if(!res.ok){
return <div>エラーが発生しました!</div>
}
return (
<SomeDescription
some={res.body}
></SomeDescription>
);
}
1番目の書き方ができてスッキリするなと思ったのに...
対応方法
-
全てのページをwrapする関数
https://github.com/vercel/next.js/discussions/62681#discussioncomment-9987827 -
digestの上書き
https://github.com/vercel/next.js/discussions/49506#discussioncomment-10120012
2つ目のリンクを見るとerrorバウンダリのエラーメッセージがproduction buildで潰されるのはnextjsではなくreactの仕様みたい
nextjsの公式的には想定されるエラーには、ちゃんとpage.tsx内でtry-catch使わずに拾いましょう、と言っている。
'use server'
import { redirect } from 'next/navigation'
export async function createUser(prevState: any, formData: FormData) {
const res = await fetch('https://...')
const json = await res.json()
if (!res.ok) {
return { message: 'Please enter a valid email' }
}
redirect('/dashboard')
}
14.2系では、UIライブラリとturbopackを一緒に使うと壊れる可能性がある
14.3系に修正が盛り込まれたので、カナリーリリース版じゃなくなればアップグレードする。
experimental.serverComponentsExternalPackages
に指定したライブラリはクライアントコンポーネントからインポートできない
v14.2.9からexperimental.serverComponentsExternalPackages
から除外する。しかしなぜこの設定にしたのか覚えていない...
const nextConfig = {
experimental: {
serverComponentsExternalPackages: [
// here
'@ts-rest/core'
],
},
};
module.exports = nextConfig;
'use client';
import { initContract } from '@ts-rest/core'; // here
console.log('initContract', initContract);
export function SomeComponent() {
const ok = () => {
};
return <button onClick={() => ok()}>ok</button>;
}
エラー内容:
⨯ Internal error: Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
TypeError: Response.clone: Body has already been consumed.というエラーが発生する
追記: 2024/12/09
バージョン14.2.14にアップグレードしても同様の現象が確認された。パラレルルーティング内でおなじサーバーアクションにアクセスしてると起こるっぽい。
Issueの中で、14.2.20で修正されたとアナウンスがあった。
Resolved in #73274. Accessible in Next.js 14.2.20 and 15.0.4-canary.40+
確認したバージョン
nextjs v14.2.8
nodejs v20.?
関連issue:
改善策:
こちらのpatchで修正されたっぽい? next v14.2.14にアップグレードする必要がある
#70082がはいったリリースバージョン