Перейти к содержимому
Hogin Hogin
Назад

Деплой Astro на Cloudflare Pages: Git-интеграция и GitHub CI/CD

10 мин чтения

Cloudflare Pages раздаёт статику с CDN в 300+ городах бесплатно, а Astro по умолчанию собирает именно статику. Связать их можно двумя способами: подключить репозиторий прямо в дашборде — и Cloudflare сам собирает и публикует на каждый push в main, без единой строчки YAML; либо собирать у себя в GitHub Actions и слать готовый dist через Wrangler, когда нужен контроль над пайплайном. Разберём оба пути, начав с того, что без CI: подключение за пять минут, свой домен и отдельное dev-окружение.

Table of contents

Open Table of contents

Два способа задеплоить на 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 по-прежнему отрендерит в статику.

Astro static против server с адаптером Cloudflare

В обоих случаях артефакт для деплоя — это каталог dist. Дальше всё одинаково.

Путь без CI: Git-интеграция за пять минут

Самый короткий путь — не писать YAML вообще, а отдать сборку Cloudflare.

В дашборде: Workers & Pages → Create → Pages → Import an existing Git repository. Подключаете GitHub (один раз выдаёте доступ к репозиторию или ко всему аккаунту), выбираете репозиторий и продакшен-ветку main. Дальше — настройки сборки:

ПолеЗначение
Framework presetAstro
Build commandpnpm build
Build output directorydist
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

Конвейер: push в main запускает сборку в GitHub Actions и публикацию через Wrangler

Что нужно, чтобы задеплоить из 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 добавьте два:

Никогда не вписывайте их в 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.

Push в main уходит в production, push в ветку — в preview

Чтобы это заработало, расширьте триггер на все ветки и пробросьте имя текущей в команду:

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 — а команда деплоя в обоих случаях одна и та же.


Поделиться:

Предыдущая статья
Свой Matrix + Element: мессенджер, который принадлежит вам
Следующая статья
HTTP/3 и QUIC простыми словами