[Next.js] アクセシビリティとレンダリングエラーの解決方法
![[Next.js] アクセシビリティとレンダリングエラーの解決方法](/assets/note/eyecatch/nextjs-accessibility-and-rendering-errors-eyecatch.png)
illustrated by soto
Next.jsプロジェクトで発生した複数のエラー
Next.jsとCloudflare Pagesで構築しているサイトで、以下のエラーが発生した。
aria-hiddenとフォーカス可能な要素の競合allowFullScreenとallow属性の競合- React error #418(Suspense境界でのテキストノード問題)
dynamic = "error"による404エラー
それぞれのエラーの原因と解決方法をまとめる。
1. aria-hiddenとフォーカス可能な要素の競合
エラー内容
Blocked aria-hidden on an element because its descendant retained focus. The focus must not be hidden from assistive technology users. Avoid using aria-hidden on a focused element or its ancestor. Consider using the inert attribute instead.
原因
ドロワーメニューが閉じている状態でaria-hidden="true"が設定されているにもかかわらず、内部のリンク(<a>タグ)がフォーカス可能な状態になっていた。
// 問題のあるコード
<nav aria-hidden={!open}>
<Link href="/">top</Link> {/* メニューが閉じていてもフォーカス可能 */}
</nav>CSSの transform: translateX(-100%) で画面外に隠しているだけで、DOMには存在しているため、フォーカスが当たってしまう。
解決方法:inert属性を使用
メニューが閉じている時に inert 属性を付与することで、フォーカスを完全にブロックする。
// 修正後
<nav
aria-label="サイト内メニュー"
className={`${styles.drawer} ${open ? styles.drawerOpen : ""}`}
{...(!open && { inert: true })}
>
<Link href="/">top</Link>
</nav>/* CSS */
.drawer {
transform: translateX(-100%);
transition: transform 0.25s ease;
}
.drawerOpen {
transform: translateX(0);
}これにより:
inert属性でメニューが閉じている時はフォーカス不可能になる- CSSトランジションによるスライドアニメーションが正常に機能
aria-hiddenをinertに置き換えることで警告を根本的に解決できる
注意: 条件付きレンダリング({open && <nav>...</nav>})を使うとアニメーションが失われるため、要素は常にDOMに存在させる必要がある。
2. allowFullScreenとallow属性の競合
エラー内容
Allow attribute will take precedence over 'allowfullscreen'.
原因
Spotify埋め込みiframeで、古いallowFullScreen属性と新しいallow属性の両方が設定されており、allow属性にfullscreenが含まれているため競合していた。
// 問題のあるコード
<iframe
allowFullScreen
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
/>解決方法:allowFullScreenを削除
現代的なベストプラクティスは allow 属性のみを使用すること。
// 修正後
<iframe
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
/>allow属性にfullscreenが含まれているため、フルスクリーン機能は引き続き有効。
3. React error #418(Suspense境界の問題)
エラー内容
Uncaught Error: Minified React error #418
原因
Client ComponentがフラグメントをルートとするとServer/Clientの境界でhydrationミスマッチが起きやすく、Suspense #418エラーに繋がる。今回はフラグメントの直下に <h1> という通常のHTML要素とフォームコンテナが混在していたことが原因だった。
// 問題のあるコード(ContactForm.tsx)
function ContactForm() {
return (
<>
<h1>Contact</h1>
<div className={styles.contactFormContainer}>
{/* フォーム内容 */}
</div>
</>
);
}解決方法:単一ルート要素を返す
Client Componentは単一のルート要素を返すようにし、<h1>はServer Component側で管理する。
// 修正後(page.tsx)
export default function ContactPage() {
return (
<main>
<h1>Contact</h1>
<ContactForm />
</main>
);
}
// 修正後(ContactForm.tsx)
function ContactForm() {
return (
<div className={styles.contactFormContainer}>
{/* フォーム内容 */}
</div>
);
}これにより:
- Server ComponentとClient Componentの境界が明確になる
- Suspenseエラーが解消される
4. dynamic = "error" による404エラー
エラー内容
GET /note/nextjs-cloudflare-edge-runtime?_rsc=icfcq 404 (Not Found) GET /schedule/live-event?_rsc=s5roe 404 (Not Found)
原因
動的ルート([slug]/page.tsx)でexport const dynamic = "error"を設定していた。これは動的レンダリングが発生した場合にエラーを投げる設定。
Next.jsのクライアントサイドナビゲーションでは、RSC(React Server Components)ペイロードを取得するために?_rsc=...クエリパラメータ付きリクエストを送信する。このリクエストが動的リクエストとして扱われ、404エラーが発生していた。
// 問題のあるコード
export const dynamic = "error"; // 動的リクエストでエラー
export async function generateStaticParams() {
const posts = await getAllPostMeta();
return posts.map(({ slug }) => ({ slug }));
}解決方法:force-staticに変更
// 修正後
export const dynamic = "force-static";
export async function generateStaticParams() {
const posts = await getAllPostMeta();
return posts.map(({ slug }) => ({ slug }));
}これにより:
- すべてのページがビルド時に静的生成される
- クライアントサイドナビゲーション時のRSCリクエストも適切に処理される
generateStaticParams()で事前に定義されたパスのみが生成される
dynamic設定の違い
Next.jsのdynamic設定には以下の選択肢がある:
| 設定値 | 動作 |
|---|---|
"auto" |
デフォルト。動的関数が使われた場合のみ動的レンダリング |
"force-static" |
すべて静的生成。動的関数使用時もビルド時の値を使用 |
"force-dynamic" |
すべて動的レンダリング(SSR) |
"error" |
動的レンダリングが発生するとエラー |
Cloudflare Pagesのような静的ホスティング環境では、"force-static"が推奨される。
まとめ
- アクセシビリティエラーは、DOM構造とARIA属性の整合性を保つことで解決
- 属性の競合は、現代的なWeb標準に従うことで解決
- Reactエラーは、Server ComponentとClient Componentの責務を明確にすることで解決
- 動的レンダリングエラーは、静的生成の設定を適切に行うことで解決
Next.js App Router + Cloudflare Pagesの構成では、これらのエラーに遭遇することが多い。
参考リンク
- Minified React error #418 - react.dev - React error #418の公式説明
- Route Segment Config: dynamic - Next.js -
dynamic設定の公式ドキュメント


