【Next.js】プレビューモードから戻れない?404エラーの原因と「解除バー」実装ガイド

【Next.js】プレビューモードから戻れない?404エラーの原因と「解除バー」実装ガイド のビジュアル

Next.jsのPreview Mode(プレビューモード)機能は便利ですが、一度有効にするとクッキーが残り続け、本番閲覧時に404エラーを引き起こすことがあります。
本記事では、ブラウザのクッキー削除操作なしで、画面上のボタンからスマートにプレビューモードを終了させる「プレビュー解除バー」の実装方法を解説します。

  • 404の原因プレビュー用クッキー(__prerender_bypass等)がブラウザに残存し、Next.jsが常にドラフトデータを要求してしまうため。
  • 解決策/api/exit-preview を叩くためのUI(解除バー)を画面に常設し、ワンクリックでクッキーを削除できるようにする。
  • 実装のポイントNext.jsのAPIルートと、条件付きレンダリングを用いたコンポーネント設計で、プレビュー中のみバーを表示させる。

Next.jsとヘッドレスCMSを組み合わせた開発において、「プレビューモード」は必須の機能です。しかし、実装中や運用中にこんなトラブルに遭遇したことはありませんか?

「プレビューを確認した後、普通にサイトを見ようとしたら404エラーになる!」 「記事を公開したはずなのに、なぜか更新が反映されない(ドラフトのまま)」 「毎回ブラウザのクッキーを削除したり、シークレットウィンドウを開くのが面倒くさい...」

これは、Next.jsのプレビュー機能特有の挙動によるものです。今回は、この「プレビューモードから抜け出せない問題」を解決し、開発者にとってもクライアントにとっても親切な「プレビュー解除バー」の実装方法をご紹介します。

なぜNext.jsのプレビュー後に404エラーや不具合が起きるのか

原因は「プレビューモード用クッキー」の残留

Next.jsのプレビューAPI(/api/preview)にアクセスすると、ブラウザには以下の特別なクッキーが保存されます。

  • __prerender_bypass
  • __next_preview_data

このクッキーが存在する限り、Next.jsはすべてのページリクエストに対して「今はプレビューモードだ」と判断し、context.preview = true として振る舞います。

その結果、通常の本番ページを見に行っても、裏側ではCMSの「下書きデータ(draftKeyあり)」を探しに行ったり、静的キャッシュ(ISR/SSG)を無視して動的取得(SSR相当)を試みたりします。ここでロジックの不整合が起きると、存在しないパスを探して404エラーになったり、表示が崩れたりするのです。

開発者やクライアントを困らせる「解除の手間」

この状態を解消するにはクッキーを削除するしかありません。しかし、非エンジニアであるクライアントに「デベロッパーツールを開いてApplicationタブから...」と説明するのは現実的ではありません。「シークレットウィンドウを使ってください」というのも、UXとしてはスマートではないでしょう。

解決策:ユーザーフレンドリーな「プレビュー解除バー」を作る

最もプロフェッショナルな解決策は、画面上に「現在プレビューモードで表示しています。[終了する]」というバーを表示させることです。

全体の実装フロー

  1. 解除用API: クッキーを削除するAPIエンドポイントを用意する(Next.js標準)。
  2. UIコンポーネント: プレビュー中のみ表示されるバーを作成する。
  3. ページ組込: 各ページでプレビュー状態を判定し、バーを呼び出す。

実装ステップ1:解除用APIエンドポイントの確認

まず、プレビューモードを終了(クッキーを削除)するためのAPIルートが必要です。Next.jsの標準的な構成であれば、pages/api/exit-preview.ts として以下のようなファイルを用意します。

import { NextApiRequest, NextApiResponse } from 'next';

export default async function exit(_: NextApiRequest, res: NextApiResponse) {
  // プレビューモードのクッキーを削除
  res.clearPreviewData();

  // 処理完了後、トップページや元のページへリダイレクト
  res.writeHead(307, { Location: '/' });
  res.end();
}

このURL /api/exit-preview にアクセスさえすれば、呪縛(クッキー)は解けます。

実装ステップ2:解除バー(UIコンポーネント)の作成

次に、このAPIを叩くためのUIコンポーネント components/ui/PreviewBanner.tsx を作成します。

TypeScript

// components/ui/PreviewBanner.tsx
import Link from 'next/link';
import styles from './PreviewBanner.module.scss';

export const PreviewBanner = () => {
  return (
    <div className={styles.banner}>
      <p>現在プレビューモードで表示しています</p>
      <Link href="/api/exit-preview" prefetch={false} className={styles.button}>
        プレビューを終了する
      </Link>
    </div>
  );
};

ポイント: prefetch={false} を指定しておきましょう。誤ってホバーしただけで解除APIが叩かれるのを防ぐためです。

スタイリング(SCSS/CSS Modules)

画面最上部に固定し、目立つ色にしておくのがおすすめです。

SCSS

// components/ui/PreviewBanner.module.scss
.banner {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 50px;
  background-color: #d63384; // マゼンタなど目立つ色
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  z-index: 9999; // 最前面に表示
  font-weight: bold;
  box-shadow: 0 2px 10px rgba(0,0,0,0.2);
}

.button {
  background: #000;
  color: #fff;
  padding: 4px 12px;
  border-radius: 4px;
  font-size: 0.9rem;
  text-decoration: none;
  transition: opacity 0.2s;
  cursor: pointer;

  &:hover {
    opacity: 0.8;
  }
}

実装ステップ3:各ページへの組み込み

最後に、詳細ページ(例: pages/insights/[slug].tsx)などでこのコンポーネントを呼び出します。 getStaticProps から渡される preview フラグを利用して、条件付きレンダリングを行います。

TypeScript

// pages/insights/[slug].tsx
import { InferGetStaticPropsType } from 'next';
import { PreviewBanner } from '@/components/ui/PreviewBanner'; // 作成したコンポーネント

// ... getStaticPropsの実装(previewフラグを返す) ...

const InsightDetail = ({ insight, preview }: InferGetStaticPropsType<typeof getStaticProps>) => {
  return (
    <>
      {/* ▼ previewがtrueの時だけバーを表示! */}
      {preview && <PreviewBanner />}

      <main>
        {/* 記事コンテンツ */}
        <h1>{insight.title}</h1>
        {/* ... */}
      </main>
    </>
  );
};

export default InsightDetail;

まとめ:開発体験(DX)と運用体験を向上させよう

プレビュー解除バーを実装することで、以下のメリットが生まれます。

  • 404エラーの撲滅: 意図しないプレビュー状態の残留を防げます。
  • クライアントへの安心感: 「確認が終わったら黒いボタンを押してください」と伝えるだけで運用が回ります。
  • 開発効率アップ: クッキー削除の手間から解放されます。

小さなUIコンポーネントですが、運用のストレスを劇的に減らす「プロのひと手間」です。ぜひ実装してみてください。

参考リンク

この記事は役に立ちましたか?