Astro 4 → 5: что сломалось и как чинить
Я мигрировал три проекта с Astro 4 на 5. Один — контент-сайт со статикой, второй — гибрид с серверным рендером и API-роутами, третий — большой монорепозиторий с собственными интеграциями. Опыт у каждого свой, но общая картина одинаковая: миграция не страшная, но без чек-листа теряется время на перерывы и поиск решения по issue-трекеру.
Здесь — что реально ломается, что хочется поправить заранее и какие шаги стоит проделать в правильном порядке.
Версии Node и пакетов
Astro 5 требует Node 18.20.8+ или 20.3+, либо 22+. Если у тебя в CI всё ещё крутится 18.0.0 — обнови до 18.20+ или сразу до 22 LTS. У меня на одном проекте именно из-за старой ноды отвалилась сборка, причём в ошибке было указано не «обновите Node», а нечитаемый stack от Vite.
Astro 5 идёт на Vite 6, поэтому интеграции, которые жёстко прибиты к Vite 4 или 5, придётся обновить. Список таких пакетов короткий, но если у вас был свой плагин — пройдитесь по changelog Vite 6 и убедитесь, что не используете удалённые API.
Конфиг: output, hybrid и prerender
Главное концептуальное изменение. В Astro 4 поле output могло быть 'static', 'server' или 'hybrid'. В Astro 5 'hybrid' исчез: теперь любой проект, где есть SSR-страницы, объявляется как output: 'server', а статичные страницы помечаются export const prerender = true.
// Astro 4
export default defineConfig({ output: 'hybrid', adapter: node({ mode: 'standalone' }) });
// Astro 5
export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }) });В каждой странице, которая раньше неявно билдилась статически, проставь prerender = true. Можно всё пометить разом через хук в middleware.ts, но я предпочитаю явный экспорт — так видно поведение по файлам.
Content Collections и content layer
Если у тебя в проекте использовались коллекции через defineCollection с локальными MDX/Markdown — на четвёрке всё работало, но на пятёрке тебе предложат перейти на новый Content Layer API.
Старый код останется работать, ты получишь предупреждение о деплое. Если коллекция простая — миграция дело десяти минут. Если сложная (с глоссарием, кастомными loader-ами через скрипты) — закладывай день на переписывание.
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const posts = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/data/posts' }),
schema: z.object({
title: z.string(),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
}),
});
export const collections = { posts };Главное отличие от старой схемы — теперь явно указываешь loader. Это полезно, потому что коллекцию можно собрать из чего угодно: API, БД, удалённого репозитория. Я этим уже воспользовался: вытаскиваю заметки из Notion-копии и сразу собираю их в коллекцию без промежуточных скриптов.
API endpoints: только заглавные методы
В Astro 4 разрешалось писать export async function get() в API-роуте. В пятёрке поддерживаются только GET, POST, PUT, DELETE, PATCH, OPTIONS, ALL. Если оставить нижний регистр — endpoint просто не зарегистрируется. Это ловится глобальным поиском по export async function (get|post|put|delete|patch)\( и заменой.
Astro DB
Если использовал @astrojs/db, в пятёрке его статус сменился. Команда отметила, что фокусируется на ядре, и активная разработка остановлена. Сами таблицы продолжают работать, но новых фич ждать не стоит. Если ты только закладываешь хранилище — присмотрись к Drizzle напрямую, выйдет надёжнее.
Image Service
В пятёрке поведение <Image /> немного изменилось: при SSR оптимизация по умолчанию делается через сервис sharp, и он выполняется в рантайме. Если у тебя был кастомный сервис с тонкой логикой кэширования — проверь, что он переписан на новый интерфейс SharedAdapter.
Самая частая проблема, которую я видел: на проде картинки отдаются медленно, потому что Sharp обрабатывает их по запросу без слоя кэширования. Решение простое — поставь перед сервером CDN, либо включи на адаптере опцию хранения предобработанных изображений.
Client islands
API клиентских островов client:idle, client:visible и client:load остались. Что изменилось — внутренний механизм генерации границы. Если у тебя были runtime-хаки, которые работали через перехват data-island-id или похожих атрибутов, они почти наверняка отвалились. Лечится переписыванием на стандартный client:*.
Чек-лист перед миграцией
- Обнови Node до 20.3+ (а лучше 22 LTS) и подними версию в CI.
- Запиши версии всех интеграций до апдейта — пригодится, если что-то откатывать.
- Перейди на
output: 'server', расставьprerender. - Проверь все API-endpoints на регистр методов.
- Перепиши коллекции на content layer, если они нетривиальные.
- Прогон визуальных регрессионных тестов после билда — даже если у тебя их два.
Шаг, который я бы не пропускал
До миграции запусти полный билд на четвёрке, сохрани dist/. После пятёрки сравни хотя бы по размерам файлов и количеству чанков. Я однажды поймал ситуацию, когда после апдейта появлялся лишний серверный entry-чанк, потому что одна страница случайно перестала быть статической. Без сравнения это бы заметили только через мониторинг производительности на проде.
В сухом остатке
Astro 5 — не революция. Это аккуратный апдейт, в котором команда давно собиралась убрать 'hybrid', перейти на новый content layer и почистить наследие. Все три проекта перевёл за совокупно три рабочих дня, и больше всего времени ушло не на миграцию как таковую, а на актуализацию интеграций сторонних пакетов. Если откладывал — самое время взяться, в шестёрке наверняка снова придётся переписывать конфиги.