Что нового в Astro 7.4

Вышел Astro 7.4, который приносит флаг `--timings` для анализа времени сборки, разделение сервисов изображений для статической и динамической генерации, обработчик `notFound()` для пользовательских точек входа и предупреждения о неподдерживаемых настройках шрифтов. В экспериментальном режиме также доступны параллельный пререндеринг и кастомная страница редиректа для статических сайтов.

Вышел Astro 7.4, который приносит флаг --timings для анализа времени сборки, разделение сервисов изображений для статической и динамической генерации, обработчик notFound() для пользовательских точек входа и предупреждения о неподдерживаемых настройках шрифтов. В экспериментальном режиме также доступны параллельный пререндеринг и кастомная страница редиректа для статических сайтов.

Анализ времени сборки с --timings

При медленной сборке не всегда очевидно, какой этап замедляет сборку: плагин Vite, подсветка синтаксиса, обработка изображений или рендеринг одной тяжёлой страницы. Флаг --timings в команде astro build решает эту проблему.

После завершения сборки Astro выводит сводку по фазам сборки, плагинам Vite, обработке Markdown, рендерингу страниц, изображениям и интеграциям.

Пример использования

На стартовом шаблоне блога добавили компонент <CommentSection>, который запрашивает комментарии, а каждый <Comment> — профиль автора:


---

const { comment } = Astro.props;

// Запрос профиля комментатора для каждого комментария

const res = await fetch(`https://jsonplaceholder.typicode.com/users?email=${comment.email}`);

const [user] = await res.json();

---

<article>

  <strong>{user?.name ?? comment.name}</strong>

  <p>{comment.body}</p>

</article>

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


build timings

total 2.61s · 9 pages · output "static"

Build phases

  resolve config                        59ms   2.3%

  setup                                225ms   8.6%

  route discovery                       2ms   0.1%

  vite config                          14ms   0.5%

  content sync                        206ms   7.9%

  prerender bundle                     151ms   5.8%

  client bundle                         21ms   0.8%

  manifest injection                     2ms   0.1%

  generate pages                      2.10s  80.6%

Page rendering (2.09s across 9 pages)

  component render                    2.08s  79.6%

  page pipeline                         13ms   0.5%

Slowest pages (median 297ms · p95 571ms)

  /about                               571ms

  /blog/first-post                     307ms   /blog/[...slug]

  /blog/using-mdx                      303ms   /blog/[...slug]

  /blog/second-post                    299ms   /blog/[...slug]

  /blog/third-post                     297ms   /blog/[...slug]

  /blog/markdown-style-guide           296ms   /blog/[...slug]

  /rss.xml                              10ms

  /blog                                  6ms

Отчёт показывает, что 81% времени уходит на генерацию страниц, причём почти всё — на рендеринг компонентов. Самые медленные страницы используют макет поста блога, в то время как /blog и /rss.xml отрисовываются за миллисекунды. Это указывает на проблемный компонент.

Параллельная загрузка комментариев и профилей через Promise.all() в <CommentSection> и передача готовых профилей в <Comment> сократила генерацию страниц с 2,10 до 1,13 секунды и вдвое ускорила каждый пост.

Отчёт также охватывает плагины Vite, самые медленные файлы для сборки, плагины Markdown, подсветку синтаксиса и трансформации изображений, поэтому флаг полезен вне зависимости от типа задержек.

Раздельные сервисы изображений для сборки и рантайма

Предварительно отрендеренные (статичные) и динамические (on-demand) страницы предъявляют разные требования к обработке изображений. Во время сборки доступны полное окружение Node.js и достаточно времени, поэтому локальный оптимизатор вроде Sharp работает отлично. Динамические страницы выполняются на сервере или в edge-окружении, где Sharp часто недоступен, и там лучше подходит CDN изображений. Раньше приходилось выбирать один сервис для обоих сценариев либо полагаться на адаптер.

Astro 7.4 позволяет указать отдельные сервисы для build и runtime:


import { defineConfig } from 'astro/config';

export default defineConfig({

  image: {

    service: {

      build: { entrypoint: 'astro/assets/services/sharp' },

      runtime: { entrypoint: 'my-image-cdn-service' },

    },

  },

});

Сервис build обрабатывает изображения на статичных страницах и во время разработки, runtime — на динамических страницах и эндпоинте изображений в продакшене. Передача одного сервиса по-прежнему работает.

Переработана внутренняя логика отслеживания используемых изображений — теперь пререндереры участвуют в общем пайплайне Astro, а не строят его заново. Благодаря этому @astrojs/cloudflare обрабатывает изображения статичных страниц через тот же пайплайн, что и другие адаптеры.

Обработчик notFound() для кастомных точек входа

Композируемые обработчики из astro/fetch и astro/hono позволяют строить собственную серверную точку входа из компонентов Astro. Однако при использовании pages() без общего обработчика astro() запросы, не совпавшие ни с одним маршрутом, возвращали пустой 404 вместо заданной страницы 404.astro.

Astro 7.4 добавляет обработчик notFound(). Достаточно разместить его перед pages(), чтобы отрисовать свою страницу 404 для всех несовпадающих запросов:


import { Hono } from 'hono';

import { notFound, pages } from 'astro/hono';

import { cf } from '@astrojs/cloudflare/hono';

const app = new Hono();

app.use(cf());

app.use(notFound());

app.use(pages());

export default app;

Обработчик доступен и в astro/fetch. Поскольку astro() уже включает эту логику, notFound() нужен только при самостоятельной компоновке обработчиков.

Предупреждения о настройках шрифтов

Запросить несуществующее начертание или подмножество шрифта легко, а ошибка часто выявляется только по некорректному отображению текста. Теперь провайдеры шрифтов могут реализовать опциональный колбэк getFontProperties(), который сообщает реально доступные веса, стили, подмножества и форматы семейства. Astro предупреждает обо всех значениях в конфигурации, которые провайдер не может обслужить.

Все встроенные провайдеры шрифтов поддерживают этот механизм. Разработчикам пользовательских провайдеров рекомендуется внедрить getFontProperties() уже сейчас, так как в Astro 8 колбэк станет обязательным:


import type { FontProvider } from 'astro';

const provider: FontProvider = {

  name: 'my-provider',

  resolveFont: ({ familyName }) => {

    // реализация

  },

  getFontProperties: ({ familyName }) => ({

    weights: ['400', '700'],

    styles: ['normal', 'italic'],

    subsets: ['latin'],

    formats: ['woff2'],

  }),

};

Контекст init() для провайдеров теперь также включает функцию fetch() с повторными попытками при временных сбоях.

Обновление минимальной версии Node.js

Минимальная поддерживаемая версия Node.js для Astro и его официальных интеграций теперь — 22.22.0 (последний доступный релиз Node.js 22, поддерживаемый StackBlitz). Это соответствует политике поддержки Node.js в Astro. Перед обновлением Astro убедитесь, что у вас установлена Node.js 22.22.0 или новее.

Экспериментальный параллельный пререндеринг

Astro мог рендерить статичные страницы конкурентно, но всё происходило в одном потоке. На больших сайтах с ресурсоёмкими страницами сборка упиралась в одно ядро процессора. Флаг experimental.parallelPrerender распределяет рендеринг по воркерам Node.js:


import { defineConfig } from 'astro/config';

export default defineConfig({

  experimental: {

    parallelPrerender: true,

  },

});

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


export default defineConfig({

  experimental: {

    parallelPrerender: {

      workers: 4,

    },

  },

});

Каждый воркер одновременно рендерит столько страниц, сколько разрешено настройкой build.concurrency. Опция игнорируется, если адаптер предоставляет собственный механизм пререндеринга (среди официальных это только @astrojs/cloudflare); в таком случае Astro выведет предупреждение.

Флаг хорошо сочетается с инкрементальными статическими сборками: неизменённые страницы пропускаются, а требующие рендеринга распределяются по ядрам.

Экспериментальная кастомная страница редиректа

Статический сайт не может отправить HTTP-заголовок Location, поэтому Astro генерирует для каждого редиректа HTML-страницу с <meta http-equiv="refresh">. Это касается настроек redirects, вызовов Astro.redirect() на пререндеренных страницах и редиректов интернационализации. Разметка, текст и задержка такой страницы были фиксированными — нельзя было изменить оформление или перевод.

Флаг experimental.redirectPage позволяет задать собственную страницу:


import { defineConfig } from 'astro/config';

export default defineConfig({

  experimental: {

    redirectPage: true,

  },

});

Затем создаётся src/pages/3xx.astro. Astro рендерит её для каждого редиректа, передавая пропсы from, to, status и delay:


---

import type { RedirectPageProps as Props } from 'astro';

const { from, to, status, delay } = Astro.props;

---

<html lang="en">

  <head>

    <meta http-equiv="refresh" content={`${delay};url=${to}`} />

    <meta name="robots" content="noindex" />

    <title>Redirecting…</title>

  </head>

  <body>

    <p>This page has moved. <a href={to}>Continue to {to}</a>.</p>

  </body>

</html>

В статической сборке именно <meta> выполняет редирект, поэтому страница должна его содержать. Если тег отсутствует, Astro предупреждает при сборке и использует встроенную страницу. Редиректы на динамических страницах работают без изменений.

Ответить