Cloudflare Pages раздаёт статику с CDN в 300+ городах бесплатно, а Astro по умолчанию собирает именно статику. Связать их можно двумя способами: подключить репозиторий прямо в дашборде — и Cloudflare сам собирает и публикует на каждый push в main, без единой строчки YAML; либо собирать у себя в GitHub Actions и слать готовый dist через Wrangler, когда нужен контроль над пайплайном. Разберём оба пути, начав с того, что без CI: подключение за пять минут, свой домен и отдельное dev-окружение.
Table of contents
Open Table of contents
- Два способа задеплоить на Pages
- Сначала определитесь с режимом Astro
- Путь без CI: Git-интеграция за пять минут
- Свой домен
- Отдельное dev-окружение: preview-деплои
- Когда переходить на GitHub Actions
- Что нужно, чтобы задеплоить из Actions
- Preview-ветки в Actions
- Как проверить, что задеплоилось
- Частые грабли
- Итог
Два способа задеплоить на Pages
У Cloudflare Pages есть два режима, и их легко перепутать.
Git-интеграция. Вы подключаете репозиторий в дашборде, Cloudflare сам клонирует код, запускает сборку на своих билдерах и публикует результат. Ноль YAML, автоматический preview на каждую ветку и pull request, push в main сразу уходит в прод. Это самый быстрый путь, и большинству проектов — блог, лендинг, документация — его более чем достаточно.
Direct Upload из своего CI. Вы собираете проект у себя — в GitHub Actions — и отправляете готовый каталог dist в Pages утилитой Wrangler. Пишете YAML и храните секреты сами, зато полностью владеете пайплайном: любая версия Node, кэш pnpm, гейт по тестам и линтерам, общие шаги с другими job’ами.
Простое правило: начинайте с Git-интеграции, переходите на Actions, когда упрётесь в её потолок — нужен прогон тестов перед публикацией, нестандартная версия Node или общий пайплайн с другими сервисами.
Сначала определитесь с режимом Astro
От этого зависит и команда сборки, и нужен ли адаптер.
Статика — output: 'static' (значение по умолчанию). Astro рендерит все страницы в HTML на этапе сборки и складывает в dist. Адаптер не нужен, Pages просто раздаёт файлы. Это случай блога, лендинга, документации — самый быстрый и дешёвый вариант, и именно он нужен большинству.
SSR — output: 'server' с адаптером @astrojs/cloudflare. Нужен, когда часть страниц рендерится на лету: личный кабинет, API-роуты, персонализация по куке. Сборка кладёт в dist не только статику, но и _worker.js — Pages Function, которая исполняется на edge. Подключается одной командой:
pnpm astro add cloudflare
Она ставит адаптер и прописывает его в astro.config.mjs. Дальше помечайте динамические страницы export const prerender = false, а остальное Astro по-прежнему отрендерит в статику.
В обоих случаях артефакт для деплоя — это каталог dist. Дальше всё одинаково.
Путь без CI: Git-интеграция за пять минут
Самый короткий путь — не писать YAML вообще, а отдать сборку Cloudflare.
В дашборде: Workers & Pages → Create → Pages → Import an existing Git repository. Подключаете GitHub (один раз выдаёте доступ к репозиторию или ко всему аккаунту), выбираете репозиторий и продакшен-ветку main. Дальше — настройки сборки:
| Поле | Значение |
|---|---|
| Framework preset | Astro |
| Build command | pnpm build |
| Build output directory | dist |
| Root directory | / (пусто) |
pnpm Cloudflare определит сам по полю packageManager в package.json — ставить его отдельно не нужно. Жмёте Save and Deploy: Cloudflare клонирует репозиторий, прогоняет pnpm build на своих билдерах и через минуту отдаёт сайт на <project>.pages.dev.
С этого момента каждый push в main собирает и публикует прод автоматически — без тегов, без ручного шага, без секретов в репозитории. Запушили в другую ветку или открыли pull request — Cloudflare соберёт её в отдельный preview (об этом ниже).
Один нюанс, на котором легко споткнуться: если в package.json зафиксирован packageManager: [email protected], ему нужен Node ≥ 22.13 — а дефолтный Node у билдеров Cloudflare старее, и сборка падает с This version of pnpm requires at least Node.js v22.13. Лечится одной переменной окружения: в Settings → Variables and Secrets добавьте NODE_VERSION со значением 22.22.2 (любая версия ≥ 22.13) и пересоберите.
Свой домен
После первого деплоя сайт живёт на <project>.pages.dev. Чтобы повесить его на свой домен: в проекте Custom domains → Set up a domain, вводите example.com. Если домен уже обслуживается в Cloudflare, нужную DNS-запись (CNAME на <project>.pages.dev, для апекса — через CNAME-flattening) дашборд создаёт сам; если домен у стороннего регистратора — Cloudflare покажет, какой CNAME прописать вручную.
TLS-сертификат выпускается автоматически и продлевается сам — настраивать ничего не нужно. Добавьте и апекс (example.com), и www, если хотите оба, и выберите главный — остальные будут редиректить на него. Каждый следующий деплой публикуется сразу на ваш домен, DNS трогать больше не придётся.
Отдельное dev-окружение: preview-деплои
Git-интеграция бесплатно даёт второе окружение. Прод — это только продакшен-ветка (main); любая другая ветка и любой pull request собираются в отдельный preview-деплой со своим адресом <hash>.<project>.pages.dev, а для именованной ветки ещё и стабильный <branch>.<project>.pages.dev. Прод при этом не трогается — удобно гонять черновики и ревью.
Окружения можно развести и по конфигу. В Settings → Variables and Secrets переменные задаются отдельно для Production и Preview: например, в preview подставить тестовый ID аналитики (или убрать его совсем) и закрыть индексацию через noindex, чтобы черновики не попали в поиск. Тот же приём — для разных API-ключей или фичефлагов.
Рабочий цикл получается такой: пушите ветку draft/new-post → Cloudflare собирает её в preview, смотрите вживую на отдельном URL → мёржите в main → прод обновляется автоматически.
Когда переходить на GitHub Actions
Git-интеграция упирается в потолок там, где нужен контроль над сборкой: прогнать тесты как гейт перед публикацией, зафиксировать точную версию Node, переиспользовать кэш и шаги вместе с другими job’ами. Тогда сборку забирают к себе в CI, а в Pages отправляют уже готовый dist.
| Git-интеграция | GitHub Actions | |
|---|---|---|
| Где идёт сборка | на билдерах Cloudflare | в вашем CI |
| Настройка | пара кликов в дашборде | YAML-файл в репозитории |
| Версия Node, кэш | ограниченно | полный контроль |
| Тесты перед деплоем | отдельно | в том же пайплайне |
| Preview на ветку | автоматически | через флаг --branch |
| Секреты | в дашборде | в GitHub Secrets |
Что нужно, чтобы задеплоить из Actions
Понадобятся четыре вещи: проект на Pages, API-токен, два секрета в репозитории и workflow-файл.
1. Создайте проект Pages. В CI деплой не должен спрашивать ничего интерактивно, поэтому проект лучше завести заранее — в дашборде (Workers & Pages → Create → Pages → Direct Upload) или одной командой локально:
npx wrangler pages project create my-astro-app \
--production-branch main
Имя my-astro-app станет поддоменом my-astro-app.pages.dev и будет фигурировать в команде деплоя.
2. Выпустите API-токен. В My Profile → API Tokens → Create Token возьмите шаблон Edit Cloudflare Pages (или кастомный токен с правом Account → Cloudflare Pages → Edit). Там же в дашборде скопируйте Account ID — он на главной странице аккаунта справа.
3. Положите секреты в репозиторий. В Settings → Secrets and variables → Actions добавьте два:
CLOUDFLARE_API_TOKEN— токен из предыдущего шага;CLOUDFLARE_ACCOUNT_ID— ваш Account ID.
Никогда не вписывайте их в YAML напрямую — токен с правом на Pages не должен лежать в истории git.
4. Добавьте workflow. Создайте файл .github/workflows/deploy.yml:
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=my-astro-app
Это всё. pnpm build зовёт astro build и кладёт сайт в dist, а wrangler-action берёт этот каталог и публикует. pnpm/action-setup без version берёт версию из поля packageManager в package.json. Команда pages deploy одинакова и для статики, и для SSR — Wrangler сам подхватит _worker.js, если он есть. На npm замените pnpm на npm ci и npm run build, шаг pnpm/action-setup уберите.
Preview-ветки в Actions
В Git-интеграции preview-деплои включены сами собой. В Actions их нужно завести руками: из коробки workflow выше публикует только в production. Wrangler разводит ветки по флагу --branch — деплой в production-ветку проекта (main) попадает в прод, любая другая уходит в preview с адресом <branch>.my-astro-app.pages.dev.
Чтобы это заработало, расширьте триггер на все ветки и пробросьте имя текущей в команду:
on:
push:
branches: ["**"]
# ...
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: >-
pages deploy dist
--project-name=my-astro-app
--branch=${{ github.ref_name }}
Теперь каждая ветка получает свой стабильный preview-адрес — удобно гонять ревью и QA до мёржа.
Как проверить, что задеплоилось
При Git-интеграции лог сборки виден прямо в дашборде, в разделе Deployments проекта; при Actions первый признак — зелёная галочка в Actions, где в конце шага деплоя Wrangler печатает URL опубликованной версии. Откройте его и сверьте содержимое.
Снаружи проверяется одной командой — у ответа Cloudflare характерные заголовки server: cloudflare и cf-ray:
curl -sI https://my-astro-app.pages.dev | grep -iE 'http/|server|cf-ray'
# HTTP/2 200
# server: cloudflare
# cf-ray: 8a1f...-DME
Если страница SSR — убедитесь, что динамика отрабатывает на edge, а не отдаёт закешированный HTML: например, выведите на странице текущее время и обновите пару раз. История всех публикаций с привязкой к коммиту и веткой лежит в дашборде, в разделе Deployments проекта — оттуда же можно откатиться на любую прошлую версию в один клик.
Частые грабли
Четыре ошибки, на которые уходит больше всего времени.
Версия Node для pnpm. Касается Git-интеграции: если package.json фиксирует packageManager: [email protected], pnpm требует Node ≥ 22.13. Дефолтный Node у билдеров Cloudflare ниже, и сборка падает с This version of pnpm requires at least Node.js v22.13. Задайте переменную NODE_VERSION (значение 22.22.2 или любое ≥ 22.13) в Settings → Variables and Secrets и пересоберите. В GitHub Actions та же история лечится строкой node-version: 22 в setup-node.
nodejs_compat для SSR. Если на SSR-странице вы используете Node-API (например, node:crypto или Buffer), деплой пройдёт, а страница упадёт в рантайме. Адаптеру @astrojs/cloudflare нужен флаг совместимости. Заведите в корне wrangler.toml:
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
Wrangler подхватит его при pages deploy.
Имя проекта. Значение --project-name должно точно совпадать с именем проекта в Cloudflare. Опечатка — и в неинтерактивном CI Wrangler не сможет уточнить, в какой проект публиковать, и шаг упадёт. Это первое, что стоит проверить при ошибке деплоя.
Каталог сборки. Astro всегда собирает в dist — если в конфиге не переопределён outDir. Команда pages deploy dist рассчитана на дефолт; меняли путь — поправьте и здесь.
Итог
У Cloudflare Pages два пути, и выбирать между ними окончательно не нужно. Git-интеграция — это пять минут в дашборде: подключили репозиторий, и каждый push в main собирается и публикуется сам, с бесплатными preview на ветки и автоматическим TLS на свой домен. Как только понадобится контроль — прогон тестов перед публикацией, нестандартная версия Node, общий пайплайн с другими сервисами — переезжаете на Direct Upload из GitHub Actions: десяток строк YAML и два секрета. Для статичного Astro хватает дефолтного output: 'static', для динамики добавляется адаптер @astrojs/cloudflare — а команда деплоя в обоих случаях одна и та же.