Как предварительно просматривать контент с помощью Preview Mode в Next.js

Примечание: Эта функция заменена на Draft Mode.

Примеры

В документации по Pages и Data Fetching мы обсуждали, как предварительно отрендерить страницу во время сборки (Static Generation) с помощью getStaticProps и getStaticPaths.

Статическая генерация полезна, когда ваши страницы получают данные из headless CMS. Однако она не идеальна, когда вы пишете черновик в вашей headless CMS и хотите предварительно просмотреть его сразу на странице. В этом случае вам нужно, чтобы Next.js рендерил эти страницы во время запроса, а не сборки, и получал черновик вместо опубликованного контента. Вам нужно, чтобы Next.js обходил Static Generation только для этого конкретного случая.

В Next.js есть функция под названием Preview Mode, которая решает эту проблему. Ниже приведены инструкции по её использованию.

Шаг 1: Создание и доступ к API-маршруту предварительного просмотра

Ознакомьтесь с документацией по API Routes, если вы не знакомы с API-маршрутами Next.js.

Сначала создайте API-маршрут для предварительного просмотра. Он может иметь любое имя, например pages/api/preview.js (или .ts, если используется TypeScript).

В этом API-маршруте вам нужно вызвать setPreviewData для объекта ответа. Аргумент для setPreviewData должен быть объектом, и он может быть использован в getStaticProps (подробнее об этом позже). Пока мы будем использовать {}.

export default function handler(req, res) {
  // ...
  res.setPreviewData({})
  // ...
}

res.setPreviewData устанавливает некоторые куки в браузере, которые включают режим предварительного просмотра. Любые запросы к Next.js, содержащие эти куки, будут рассматриваться как режим предварительного просмотра, и поведение для статически сгенерированных страниц изменится (подробнее об этом позже).

Вы можете проверить это вручную, создав API-маршрут, как показано ниже, и обратившись к нему вручную из браузера:

pages/api/preview.js
// Простой пример для ручного тестирования из браузера.
export default function handler(req, res) {
  res.setPreviewData({})
  res.end('Режим предварительного просмотра включён')
}

Если вы откроете инструменты разработчика в браузере и перейдёте по адресу /api/preview, вы заметите, что куки __prerender_bypass и __next_preview_data будут установлены для этого запроса.

Безопасный доступ из вашей Headless CMS

На практике вы захотите вызывать этот API-маршрут безопасно из вашей headless CMS. Конкретные шаги будут зависеть от используемой headless CMS, но вот некоторые общие действия, которые вы можете предпринять.

Эти шаги предполагают, что ваша headless CMS поддерживает настройку пользовательских URL для предварительного просмотра. Если нет, вы всё равно можете использовать этот метод для защиты URL предварительного просмотра, но вам нужно будет создавать и обращаться к URL предварительного просмотра вручную.

Во-первых, создайте секретный токен с помощью генератора токенов. Этот секрет будет известен только вашему приложению Next.js и вашей headless CMS. Это предотвратит доступ к URL предварительного просмотра для тех, у кого нет доступа к CMS.

Во-вторых, если ваша headless CMS поддерживает настройку пользовательских URL для предварительного просмотра, укажите следующий URL в качестве URL предварительного просмотра. Предполагается, что ваш API-маршрут предварительного просмотра находится по адресу pages/api/preview.js.

Терминал
https://<ваш-сайт>/api/preview?secret=<токен>&slug=<путь>
  • <ваш-сайт> должен быть вашим доменом развертывания.
  • <токен> должен быть заменён на сгенерированный секретный токен.
  • <путь> должен быть путём к странице, которую вы хотите просмотреть. Если вы хотите просмотреть /posts/foo, то следует использовать &slug=/posts/foo.

Ваша headless CMS может позволять включать переменную в URL предварительного просмотра, чтобы <путь> мог быть установлен динамически на основе данных CMS, например: &slug=/posts/{entry.fields.slug}

Наконец, в API-маршруте предварительного просмотра:

  • Проверьте, что секрет совпадает и что параметр slug существует (если нет, запрос должен завершиться ошибкой).
  • Вызовите res.setPreviewData.
  • Затем перенаправьте браузер на путь, указанный в slug. (В следующем примере используется 307 редирект).
export default async (req, res) => {
  // Проверка секрета и параметров
  // Этот секрет должен быть известен только этому API-маршруту и CMS
  if (req.query.secret !== 'MY_SECRET_TOKEN' || !req.query.slug) {
    return res.status(401).json({ message: 'Неверный токен' })
  }

  // Запрос к headless CMS для проверки существования указанного `slug`
  // getPostBySlug реализует необходимую логику запроса к headless CMS
  const post = await getPostBySlug(req.query.slug)

  // Если slug не существует, предотвращаем включение режима предварительного просмотра
  if (!post) {
    return res.status(401).json({ message: 'Неверный slug' })
  }

  // Включаем режим предварительного просмотра, устанавливая куки
  res.setPreviewData({})

  // Перенаправляем на путь из полученного поста
  // Мы не перенаправляем на req.query.slug, чтобы избежать уязвимостей открытого перенаправления
  res.redirect(post.slug)
}

Если всё прошло успешно, браузер будет перенаправлен на путь, который вы хотите просмотреть, с установленными куками режима предварительного просмотра.

Шаг 2: Обновление getStaticProps

Следующий шаг — обновить getStaticProps для поддержки режима предварительного просмотра.

Если вы запрашиваете страницу, которая имеет getStaticProps, с установленными куками режима предварительного просмотра (через res.setPreviewData), то getStaticProps будет вызван во время запроса (а не во время сборки).

Кроме того, он будет вызван с объектом context, где:

  • context.preview будет true.
  • context.previewData будет таким же, как аргумент, переданный в setPreviewData.
export async function getStaticProps(context) {
  // Если вы запрашиваете эту страницу с установленными куками режима предварительного просмотра:
  //
  // - context.preview будет true
  // - context.previewData будет таким же,
  //   как аргумент, переданный в `setPreviewData`.
}

Мы использовали res.setPreviewData({}) в API-маршруте предварительного просмотра, поэтому context.previewData будет {}. Вы можете использовать это для передачи информации о сеансе из API-маршрута предварительного просмотра в getStaticProps, если это необходимо.

Если вы также используете getStaticPaths, то context.params также будет доступен.

Получение данных для предварительного просмотра

Вы можете обновить getStaticProps для получения разных данных в зависимости от context.preview и/или context.previewData.

Например, ваша headless CMS может иметь другой API-эндпоинт для черновиков постов. В этом случае вы можете использовать context.preview для изменения URL API-эндпоинта, как показано ниже:

export async function getStaticProps(context) {
  // Если context.preview равен true, добавляем "/preview" к API-эндпоинту
  // для запроса данных черновика вместо опубликованных данных. Это может варьироваться
  // в зависимости от используемой headless CMS.
  const res = await fetch(`https://.../${context.preview ? 'preview' : ''}`)
  // ...
}

Вот и всё! Если вы обращаетесь к API-маршруту предварительного просмотра (с secret и slug) из вашей headless CMS или вручную, вы теперь должны видеть контент для предварительного просмотра. И если вы обновите черновик без публикации, вы сможете просмотреть изменения.

Установите это как URL предварительного просмотра в вашей headless CMS или обращайтесь вручную, и вы сможете видеть предварительный просмотр.

Терминал
https://<ваш-сайт>/api/preview?secret=<токен>&slug=<путь>

Дополнительные детали

Полезно знать: во время рендеринга next/router предоставляет флаг isPreview, см. документацию по объекту router для получения дополнительной информации.

Указание длительности режима предварительного просмотра

setPreviewData принимает необязательный второй параметр, который должен быть объектом опций. Он принимает следующие ключи:

  • maxAge: Указывает число (в секундах), на которое должен длиться сеанс предварительного просмотра.
  • path: Указывает путь, к которому применяются куки. По умолчанию /, что включает режим предварительного просмотра для всех путей.
setPreviewData(data, {
  maxAge: 60 * 60, // Куки режима предварительного просмотра истекают через 1 час
  path: '/about', // Куки режима предварительного просмотра применяются к путям с /about
})

Очистка куков режима предварительного просмотра

По умолчанию для куков режима предварительного просмотра не установлена дата истечения, поэтому сеанс предварительного просмотра завершается при закрытии браузера.

Чтобы очистить куки режима предварительного просмотра вручную, создайте API-маршрут, который вызывает clearPreviewData():

pages/api/clear-preview-mode-cookies.js
export default function handler(req, res) {
  res.clearPreviewData({})
}

Затем отправьте запрос к /api/clear-preview-mode-cookies для вызова API-маршрута. Если вы вызываете этот маршрут с помощью next/link, вы должны передать prefetch={false}, чтобы предотвратить вызов clearPreviewData во время предварительной загрузки ссылки.

Если в вызове setPreviewData был указан путь, вы должны передать тот же путь в clearPreviewData:

pages/api/clear-preview-mode-cookies.js
export default function handler(req, res) {
  const { path } = req.query

  res.clearPreviewData({ path })
}

Ограничения размера previewData

Вы можете передать объект в setPreviewData и получить его в getStaticProps. Однако, поскольку данные будут храниться в куки, есть ограничение по размеру. В настоящее время данные для предварительного просмотра ограничены 2 КБ.

Работа с getServerSideProps

Режим предварительного просмотра также работает с getServerSideProps. Он также будет доступен в объекте context, содержащем preview и previewData.

Полезно знать: Не следует устанавливать заголовок Cache-Control при использовании режима предварительного просмотра, так как его нельзя обойти. Вместо этого мы рекомендуем использовать ISR.

Работа с API-маршрутами

API-маршруты будут иметь доступ к preview и previewData через объект запроса. Например:

export default function myApiRoute(req, res) {
  const isPreview = req.preview
  const previewData = req.previewData
  // ...
}

Уникальность для каждого next build

Значение куки для обхода и приватный ключ для шифрования previewData изменяются после завершения next build. Это гарантирует, что куки для обхода не могут быть угаданы.

Полезно знать: Для тестирования режима предварительного просмотра локально через HTTP ваш браузер должен разрешать сторонние куки и доступ к локальному хранилищу.