16. Загрузка данных
Одна из суперсил Astro — загрузка данных прямо в фронтматере компонента с помощью top-level await. Никаких хуков, никаких useEffect — просто await fetch() на этапе рендеринга.
Думай о фронтматере Astro как о серверном скрипте: он выполняется один раз (при сборке или запросе), имеет доступ к Node.js API и может делать любые асинхронные операции.
Top-level await в фронтматере
Заголовок раздела «Top-level await в фронтматере»---// Это выполняется на сервере / при сборкеconst response = await fetch('https://api.example.com/posts');const posts = await response.json();---
<ul> {posts.map(post => <li>{post.title}</li>)}</ul>Режимы загрузки данных
Заголовок раздела «Режимы загрузки данных»Build-time (статическая генерация)
Заголовок раздела «Build-time (статическая генерация)»---// Выполняется ОДИН РАЗ при npm run buildconst posts = await fetch('https://api.example.com/posts').then(r => r.json());// Результат встраивается в HTML навсегда---Плюсы: молниеносная загрузка, CDN, нет нагрузки на сервер.
Минусы: данные устаревают до следующего билда.
Server-side (SSR)
Заголовок раздела «Server-side (SSR)»---// astro.config.mjs: output: 'server'// Выполняется при КАЖДОМ запросеconst posts = await fetch('https://api.example.com/posts').then(r => r.json());// Данные всегда свежие---Плюсы: свежие данные, персонализация, доступ к cookies/headers.
Минусы: задержка на каждый запрос.
Параллельная загрузка с Promise.all
Заголовок раздела «Параллельная загрузка с Promise.all»---// ❌ Медленно — запросы идут последовательноconst posts = await fetch('/api/posts').then(r => r.json());const user = await fetch('/api/user').then(r => r.json());const config = await fetch('/api/config').then(r => r.json());
// ✅ Быстро — все запросы параллельноconst [posts, user, config] = await Promise.all([ fetch('/api/posts').then(r => r.json()), fetch('/api/user').then(r => r.json()), fetch('/api/config').then(r => r.json()),]);---Обработка ошибок
Заголовок раздела «Обработка ошибок»---let posts = [];let error = null;
try { const res = await fetch('https://api.example.com/posts'); if (!res.ok) throw new Error(\`HTTP \${res.status}\`); posts = await res.json();} catch (e) { error = e.message; console.error('Ошибка загрузки:', e);}---
{error ? ( <p class="error">Ошибка: {error}</p>) : ( <ul>{posts.map(p => <li>{p.title}</li>)}</ul>)}Переменные окружения для API-ключей
Заголовок раздела «Переменные окружения для API-ключей»---// .env файл:// PUBLIC_API_URL=https://api.example.com// SECRET_API_KEY=super-secret-key
const apiUrl = import.meta.env.PUBLIC_API_URL;const apiKey = import.meta.env.SECRET_API_KEY; // Только в фронтматере!
const data = await fetch(\`\${apiUrl}/posts\`, { headers: { Authorization: \`Bearer \${apiKey}\` }}).then(r => r.json());---PUBLIC_* — доступны в браузере, остальные — только на сервере.
Источники данных
Заголовок раздела «Источники данных»---// CMS (Contentful, Sanity, Strapi)const { items } = await fetch(\`https://cdn.contentful.com/spaces/\${spaceId}/entries\`) .then(r => r.json());
// База данных (через Astro DB или Prisma)import { db, Post } from 'astro:db';const posts = await db.select().from(Post);
// Файловая система (через Content Collections)import { getCollection } from 'astro:content';const posts = await getCollection('blog');
// GraphQLconst { data } = await fetch('https://api.example.com/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: '{ posts { title } }' }),}).then(r => r.json());---