[Next.js] ¿No puedes salir del modo de vista previa? Causas del error 404 y guía para implementar una 'barra de desactivación'

[Next.js] ¿No puedes salir del modo de vista previa? Causas del error 404 y guía para implementar una 'barra de desactivación' のビジュアル

La función de Modo de Vista Previa de Next.js es útil, pero una vez activada, las cookies pueden persistir y causar un error 404 al visualizar el sitio en producción.
Este artículo explica cómo implementar una 'barra de desactivación de vista previa' para salir de forma elegante del modo de vista previa desde un botón en pantalla, sin tener que eliminar manualmente las cookies del navegador.

  • 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コンポーネントですが、運用のストレスを劇的に減らす「プロのひと手間」です。ぜひ実装してみてください。

参考リンク

¿Fue útil este artículo?