Назад к блогу

Пустые переведенные посты блога при развертывании на основе манифеста

2025-10-072 min read

Проблема

После развертывания на Cloudflare переведенные посты блога (на испанском, немецком, французском и т. д.) случайным образом загружались с пустым содержимым. Иногда они работали нормально, иногда нет. Страница отображалась, но контент отсутствовал.

Что такое манифест-файл?

Мои посты блога хранятся в виде JSON-файлов в кодовой базе, а не в базе данных. Это позволяет мне писать контент в моем редакторе и отслеживать все в Git. Просто.

Манифест-файл — это предварительно сгенерированный JSON-файл, содержащий все данные постов блога. У Cloudflare Workers нет доступа к традиционной файловой системе во время выполнения, они могут использовать только файлы, скомпилированные во время сборки. Поэтому вместо чтения отдельных файлов постов во время выполнения процесс сборки создает единый манифест со всеми постами.

Манифест генерируется на этапе сборки и включается в развертывание. Во время выполнения код считывает данные из этого манифеста, а не из файловой системы.

Причина

Структура моего манифеста выглядит так:

{
"slug": "my-post",
"translatedSlugs": {
"en": "my-post",
"es": "mi-publicacion"
},
"localized": {
"es": { "title": "...", "content": "..." },
"de": { "title": "...", "content": "..." }
}
}

Обратите внимание: английский язык НЕ находится в объекте localized. Английский контент находится на базовом уровне.

Моя функция getPostByTranslatedSlug делала следующее:

// Получаем пост с английской локалью для доступа к translatedSlugs
const post = getPostBySlug(slug, 'en');

// Проверяем, совпадают ли translatedSlugs...
if (post.translatedSlugs[someLocale] === translatedSlug) {
return getPostBySlug(slug, requestedLocale);
}

Когда я вызывал getPostBySlug(slug, 'en'), он пытался получить доступ к manifest.localized.en, которого не существует, поэтому возвращал пустое содержимое.

Исправление

Вместо вызова getPostBySlug(slug, 'en') я изменил код, чтобы использовать getAllPosts(), который возвращает все посты с основной метаинформацией, включая translatedSlugs:

export function getPostByTranslatedSlug(translatedSlug: string, locale: string): BlogPost | null {
// Получаем все посты для доступа к основной метаинформации
const allPosts = getAllPosts();

for (const post of allPosts) {
if (post.translatedSlugs) {
for (const [lang, slugValue] of Object.entries(post.translatedSlugs)) {
if (slugValue === translatedSlug) {
// Нашли! Теперь получаем локализованную версию
return getPostBySlug(post.slug, locale);
}
}
}
}
return null;
}

Является ли использование манифест-файла хорошей практикой?

Это зависит от вашей целевой платформы развертывания:

Для Cloudflare Workers / Edge-платформ: Да, это необходимо. Эти платформы не имеют доступа к традиционной файловой системе, поэтому вам необходимо скомпилировать все данные на этапе сборки.

Для традиционных Node.js-серверов (Vercel, AWS и т. д.): Не требуется. Вы можете считывать данные из файловой системы во время выполнения, что проще и не требует шага сборки для генерации манифеста.

Компромисс: манифест-файлы усложняют процесс сборки и могут вызывать такие ошибки, как эта, если их структура не продумана. Но они позволяют развертывание на граничных платформах, которые быстрее и дешевле.

Извлеченный урок

При использовании манифеста для статических экспортов:

  • Не предполагайте, что базовая локаль существует в localized
  • Основная метаинформация (например, translatedSlugs) находится на корневом уровне
  • В localized находятся только не базовые локали

Оставайтесь в курсе

Получайте последние статьи и идеи в свой почтовый ящик.

Unsubscribe anytime. No spam, ever.