Олег Марков
Деплой на статический хостинг static hosting
Введение
Деплой на статический хостинг стал стандартным способом публикации веб‑приложений, особенно когда речь идет о SPA, документации, лендингах и блогах. Концепция проста: вы собираете проект в набор статических файлов (HTML, CSS, JS, изображения), а хостинг отдает их как есть, без серверной логики и баз данных.
Смотрите, я покажу вам, как на практике выглядит весь путь: от подготовки сборки до загрузки на разные типы хостингов — от GitHub Pages до S3 и специализированных сервисов. Мы разберем, какие файлы нужно деплоить, как настроить роутинг для SPA, как подключить CDN и HTTPS, и чего нужно избегать, чтобы сайт не сломался после публикации.
Что такое статический хостинг
Ключевая идея
Статический хостинг — это платформа, которая:
- хранит готовые файлы вашего сайта (HTML, CSS, JS, медиа);
- отдает их по HTTP без выполнения серверного кода;
- часто автоматически подключает CDN и HTTPS.
Проще говоря, вместо того чтобы запускать серверное приложение (например, на Node.js или PHP), вы кладете уже собранные файлы, и хостинг просто отдает их пользователям.
Чем отличается от классического сервера
Давайте сравним, чтобы было понятнее.
Традиционный серверный хостинг
- Есть исполняемый код на сервере — PHP, Node.js, Python и т.д.
- Сервер обрабатывает запросы, обращается к БД, генерирует HTML динамически.
- Вам нужно управлять:
- конфигурацией сервера (Nginx, Apache);
- обновлениями окружения;
- безопасностью серверного кода.
Статический хостинг
- Сервер не запускает ваш код, он только отдает файлы.
- Вся логика работает в браузере с помощью JavaScript или внешних API.
- Вы управляете только:
- сборкой фронтенда;
- файлами, которые нужно загрузить;
- иногда — минимальной конфигурацией роутинга и заголовков.
Когда статический хостинг подходит
Он хорошо подходит, если:
- У вас SPA на React, Vue, Angular, Svelte и т.п.
- У вас документация (например, Docusaurus, MkDocs, VuePress).
- У вас блог или сайт, который генерируется статически (Next.js Static Export, Gatsby, Hugo, Jekyll).
- Лэндинги и промо‑страницы.
Если ваш бэкенд — это отдельное API (REST, GraphQL), то фронтенд как раз удобно разместить на статическом хостинге, а API — на другом сервисе (например, serverless‑функции, отдельный сервер и т.д.).
Что именно деплоится на статический хостинг
Билд проекта
Смотрите, важный момент — на статический хостинг вы никогда не грузите весь исходный код проекта. Сначала вы собираете билд, то есть итоговый набор файлов для продакшена.
Типично это:
- index.html и другие HTML (если есть);
- .js файлы (бандлы, чанки);
- .css файлы;
- шрифты, изображения, иконки;
- иногда — манифесты, сервис‑воркеры (для PWA).
Пример: React (Create React App)
Команда сборки:
# Собираем проект в продакшен
npm run build
После этого в папке build будут лежать файлы, которые нужно деплоить.
Структура может быть такой:
build/
index.html
static/
js/
main.123abc.js
css/
main.123abc.css
asset-manifest.json
favicon.ico
logo192.png
Комментарии к этому процессу:
- index.html — основной файл, который стоит в корне и загружается первым.
- static/js и static/css — скомбинированные и минифицированные файлы.
- asset‑manifest.json — служебный файл, обычно сам вам не нужен.
На статический хостинг вы загружаете все содержимое папки build (но не саму папку, если хостинг ожидает файлы прямо в корне).
Пример: Vite
Здесь процесс похожий:
# Запуск сборки для продакшена
npm run build
По умолчанию результат окажется в папке dist.
dist/
index.html
assets/
index-xxxxx.js
index-xxxxx.css
Все файлы из dist отправляются на хостинг.
Подготовка проекта к деплою
Настройка базового пути (base path)
Если ваш сайт будет открываться не с корня домена, а из поддиректории (например, https://example.com/myapp), нужно заранее это учесть при сборке.
React (Create React App)
В package.json есть поле homepage. Обратите внимание, как оно влияет на пути к статике.
{
"name": "my-app",
"homepage": "https://example.com/myapp",
"scripts": {
"build": "react-scripts build"
}
}
// Здесь мы указываем поле homepage // Оно нужно, чтобы пути до файлов статики строились корректно // Например, /static/js/main.js превратится в /myapp/static/js/main.js
Vite
В Vite используется опция base.
vite.config.js:
import { defineConfig } from 'vite'
export default defineConfig({
// Здесь мы говорим Vite, что приложение будет доступно по пути /myapp/
base: '/myapp/'
})
Если этого не сделать, ссылки на скрипты и стили могут не совпасть с реальным путем на хостинге, и сайт просто не загрузится.
Переменные окружения
На статическом хостинге переменные окружения подставляются на этапе сборки, а не при выполнении на сервере. Это важно: вы не сможете «поменять переменную» без пересборки, если только не используете внешний конфиг.
Например, в Vite:
# Переменная окружения при сборке
VITE_API_URL=https://api.example.com npm run build
В коде:
// Здесь мы читаем переменную окружения, подставленную при сборке
const apiUrl = import.meta.env.VITE_API_URL
// Обратите внимание - значение подставится прямо в код при сборке // На самом хостинге поменять его без новой сборки нельзя
То же самое в CRA или других сборщиках — обычно используются переменные, начинающиеся с REACTAPP или аналогичных префиксов.
Базовый деплой на популярные статические хостинги
GitHub Pages
GitHub Pages удобен, когда исходный код уже лежит в репозитории GitHub.
Подход 1 — деплой из ветки gh-pages
- Собираете проект (например, npm run build).
- Кладете содержимое билда в ветку gh-pages.
- В настройках репозитория указываете, что сайт берется из ветки gh-pages.
Чтобы упростить, можно использовать пакет gh-pages.
package.json:
{
"name": "my-app",
"homepage": "https://username.github.io/my-app",
"scripts": {
"build": "react-scripts build",
"deploy": "npm run build && gh-pages -d build"
}
}
// Скрипт deploy сначала собирает проект // Затем gh-pages публикует содержимое папки build в ветку gh-pages
Дальше вы делаете:
npm install --save-dev gh-pages
npm run deploy
GitHub создаст страницу по адресу, указанному в homepage.
Подход 2 — GitHub Actions
Теперь давайте посмотрим, как сделать автоматический деплой при пуше в main.
Создадим файл .github/workflows/deploy.yml:
name: Deploy to GitHub Pages
on:
push:
branches:
- main # Запускать деплой при пуше в ветку main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
# Здесь мы получаем код из репозитория
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
# Устанавливаем нужную версию Node.js
- name: Install dependencies
run: npm ci
# Ставим зависимости проекта
- name: Build project
run: npm run build
# Собираем фронтенд
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./build
# Публикуем содержимое папки build в GitHub Pages
Как видите, все шаги достаточно прозрачны: код берется из репозитория, собирается и отправляется на GitHub Pages.
Netlify
Netlify — специализированный статический хостинг с удобным интерфейсом и CI/CD.
Деплой через Git‑репозиторий
- Создаете аккаунт на Netlify.
- Нажимаете кнопку "Add new site from Git".
- Подключаете репозиторий (GitHub, GitLab, Bitbucket).
- Указываете:
- Build command — например, npm run build;
- Publish directory — например, build или dist.
Netlify сам:
- будет запускать сборку при каждом пуше;
- публиковать результат;
- настраивать HTTPS.
Конфигурация netlify.toml
Теперь вы увидите пример файла конфигурации netlify.toml, который можно положить в корень репозитория.
[build]
# Здесь указываем команду сборки
command = "npm run build"
# Здесь указываем папку с результатом сборки
publish = "build"
[[redirects]]
# Этот блок нужен для SPA с клиентским роутингом
from = "/*"
to = "/index.html"
status = 200
Комментарии:
- command — команда, которая собирает проект.
- publish — папка, содержимое которой будет отдаваться как сайт.
- redirects с from="/*" и to="/index.html" — обязательная вещь для SPA, чтобы запросы на вложенные маршруты отдавали index.html, а не 404.
Vercel
Vercel идеально подходит для проектов на Next.js, но также умеет деплоить чистый фронтенд.
Автоматический деплой
- Логинитесь на Vercel.
- Нажимаете "New Project".
- Подключаете репозиторий.
- Vercel сам распознает фреймворк и настройки.
Для SPA или статических сайтов:
- Build command — npm run build (или аналогично);
- Output directory — dist или build.
Vercel также автоматически:
- создаст префиксный домен вида projectname.vercel.app;
- подключит HTTPS;
- будет деплоить превью для pull‑request.
Деплой на S3 + CloudFront (AWS)
Когда требуется более контролируемая и масштабируемая инфраструктура, часто используют S3 как хранилище статических файлов, а CloudFront — как CDN.
Структура
- S3 bucket — в нем лежат файлы сайта.
- CloudFront distribution — отдает файлы из бака S3 через CDN.
Шаг 1. Создание и настройка S3 bucket
- Создаете бакет, например, my-static-site.
- Включаете static website hosting.
- Задаете index document: index.html.
- Настраиваете политику доступа (чтобы файлы были доступны публично или через CloudFront).
Пример политики для публичного чтения
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-static-site/*"
}
]
}
// Эта политика разрешает всем читать файлы из бакета // Будьте аккуратны - в бакете не должно быть приватных данных
Шаг 2. Загрузка файлов
Допустим, билд у вас в папке dist.
# Здесь мы заливаем содержимое папки dist в бакет S3
aws s3 sync dist/ s3://my-static-site --delete
// Параметр --delete удаляет из бакета файлы, которых больше нет в dist // Это помогает не накапливать старый мусор от предыдущих сборок
Шаг 3. CloudFront
- Создаете CloudFront distribution.
- В качестве Origin выбираете свой S3 bucket.
- В настройках Default root object указываете index.html.
Для SPA дополнительно нужно настроить поведение ошибок:
- 404 и 403 ошибки перенаправлять на /index.html с кодом 200.
Это примерно так делается в настройках Error Pages:
- HTTP error code: 404
- Customize error response: Yes
- Response page path: /index.html
- HTTP response code: 200
То же самое для 403, если нужно.
Автоматизация деплоя в AWS
Покажу вам простой пример скрипта деплоя на Bash.
#!/usr/bin/env bash
# Скрипт деплоя статического сайта на S3 и сброса кэша CloudFront
set -e
BUCKET_NAME="my-static-site"
DISTRIBUTION_ID="E1234567890"
# 1. Сборка проекта
npm ci
npm run build
# 2. Загрузка файлов в S3
aws s3 sync dist/ s3://$BUCKET_NAME --delete
# 3. Инвалидирование кэша CloudFront
aws cloudfront create-invalidation \
--distribution-id $DISTRIBUTION_ID \
--paths "/*"
// Скрипт останавливается при первой ошибке (set -e) // После загрузки файлов мы инвалидаем кэш CDN, // чтобы пользователи сразу увидели новую версию сайта
Настройка маршрутизации для SPA на статическом хостинге
Проблема с прямыми переходами по URL
Представьте SPA на React Router: у вас есть маршруты /, /about, /profile. Локально все работает, потому что dev‑сервер настроен правильно. Но после деплоя:
- Переход через ссылки внутри приложения работает;
- Прямой переход по адресу https://example.com/about может давать 404.
Это происходит потому, что статический хостинг:
- ищет файл /about/index.html или about.html;
- не находит его;
- отдает 404.
Нам нужно настроить хостинг так, чтобы любой неизвестный путь отдавал index.html.
Решения на разных платформах
Netlify
Мы уже смотрели пример в netlify.toml, но давайте еще раз:
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
// Здесь мы говорим - все пути должны возвращать index.html // Статус 200 говорит браузеру - это не ошибка, все в порядке
Vercel
В Vercel можно использовать vercel.json:
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}
// Любой путь будет переписан на index.html // Это нужно именно для SPA с клиентским роутером
GitHub Pages
С GitHub Pages немного сложнее, потому что прямой конфиг роутинга отсутствует. Есть два подхода.
- Использовать 404.html, который переадресует на index.html.
В корень билда кладем 404.html с JavaScript-перенаправлением.
<!-- Простой пример 404.html для SPA на GitHub Pages -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
// Здесь мы сохраняем путь запроса
var redirect = window.location.pathname
// Перенаправляем на index.html с хэшем
// чтобы клиентский роутер мог прочитать путь
window.location.replace('/my-app/#' + redirect)
</script>
</head>
<body>
</body>
</html>
- Использовать хэш‑роутинг (React Router HashRouter) вместо History API.
S3 + CloudFront
Мы уже упоминали: в CloudFront настраиваем Error Pages, чтобы 404/403 отдавали index.html с кодом 200. Это по сути и есть настройка роутинга для SPA.
Кеширование и обновление версии приложения
Кеширование статических файлов
Статический хостинг почти всегда настроен на агрессивное кеширование, особенно если есть CDN. Это хорошо для скорости, но плохо, когда вы выкатываете новую версию сайта, а пользователь продолжает видеть старую.
Обычно применяют стратегию:
- HTML файлы кешируются с небольшим сроком или без кеширования.
- JS и CSS файлы кешируются надолго, но их имена содержат хэш содержимого.
Пример с Vite или CRA
После сборки вы увидите файлы вроде main.123abc.js. При изменении кода хэш меняется, и имя файла становится main.456def.js.
Давайте посмотрим, как это помогает:
- Браузер получает новый index.html.
- В HTML ссылку на скрипт уже прописана с новым именем файла.
- Браузер качает новый скрипт, потому что URL изменился.
Отсюда важно:
- Не отключать хэширование имен файлов в продакшене.
- Не нарушать связи HTML ↔ JS/CSS при деплое (загружать все файлы из билда как есть).
Обновление на S3 и CloudFront
Как вы видели выше, для CloudFront обычно делают invalidation, чтобы обновления стали заметны сразу.
Если этого не сделать:
- CDN может еще некоторое время отдавать старые версии HTML.
- JS и CSS с хэшами могут оставаться актуальными сами по себе, но если HTML кэшируется, пользователь может не получить новую версию сайта.
Автоматизация деплоя: CI/CD
Зачем автоматизировать деплой
Ручной деплой через FTP или консоль быстро становится источником ошибок:
- можно забыть собрать проект;
- можно загрузить не те файлы;
- можно сломать права доступа или структуру.
CI/CD‑подход решает это:
- деплой запускается автоматически при каждом пуше;
- сборка происходит в чистом окружении;
- есть лог всех действий.
Пример: GitHub Actions + Netlify CLI
Допустим, вы хотите деплоить на Netlify, но не через Git‑интеграцию, а через CLI.
Шаг 1. Установка Netlify CLI
Локально:
npm install -D netlify-cli
Шаг 2. Токен и сайт ID
На Netlify создаете токен, сохраняете его в секрет GitHub как NETLIFYAUTHTOKEN. Также берете site ID и сохраняете как NETLIFYSITEID.
Шаг 3. Workflow
Файл .github/workflows/deploy-netlify.yml:
name: Deploy to Netlify
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy with Netlify CLI
run: npx netlify deploy --dir=dist --prod --site=$NETLIFY_SITE_ID
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
// Здесь мы используем Netlify CLI для деплоя // --dir указывает папку с билдом // --prod говорит - это продакшен деплой, а не превью
Пример: GitLab CI + S3
Если проект в GitLab, можно настроить деплой прямо оттуда.
.gitlab-ci.yml:
stages:
- build
- deploy
variables:
AWS_DEFAULT_REGION: eu-central-1
build:
stage: build
image: node:20
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
# Здесь мы собираем проект и сохраняем dist как артефакт
deploy:
stage: deploy
image: python:3.11
script:
- pip install awscli
- aws s3 sync dist/ s3://my-static-site --delete
dependencies:
- build
only:
- main
# Здесь мы ставим awscli и заливаем содержимое dist в S3
Безопасность и ограничения статического хостинга
Что нельзя сделать напрямую
Статический хостинг не выполняет серверный код, поэтому:
- нельзя напрямую подключиться к базе данных с фронтенда (это и небезопасно, и технически неправильно);
- нельзя безопасно хранить секреты на фронтенде (они будут видны пользователю в JS);
- нельзя выполнять тяжелые серверные вычисления на стороне хостинга.
Если вам нужно:
- авторизация;
- работа с платежами;
- хранение данных пользователей;
- сложная логика с приватной бизнес‑логикой;
то это выносится в отдельный бэкенд:
- классический сервер (Node.js, Django и т.д.);
- serverless‑функции (Netlify Functions, Vercel Functions, AWS Lambda);
- внешние сервисы (Auth0, Firebase, Supabase, платежные шлюзы).
Работа с секретами
Смотрите, важный принцип: все, что попадет в JavaScript‑бандл, потенциально видно пользователю. Поэтому:
- API ключи, которые должны быть приватными, нельзя зашивать в фронтенд;
- секреты лучше хранить в переменных окружения на бэкенде или в конфиге serverless‑функций.
Практический чек‑лист перед деплоем
Чтобы вам было проще, давайте соберем все ключевые моменты в одном списке:
Проект собирается в статический билд:
- Убедитесь, что команда сборки (npm run build или аналогичная) работает без ошибок.
- Проверьте размер бандлов (слишком большие файлы могут сильно замедлить загрузку).
Правильно настроен base path:
- Если сайт будет в поддиректории, настройте homepage или base.
Настроены переменные окружения:
- Все URL API и ключи, которые можно показывать, заданы правильно.
- Нет жестко зашитых локальных URL вроде http://localhost:3000 в продакшене.
Роутинг SPA:
- Для Netlify, Vercel, S3, CloudFront — настроены переадресации на index.html.
- Для GitHub Pages — либо используете 404.html с редиректом, либо хэш‑роутинг.
Кеширование:
- Включено хэширование имен JS/CSS файлов.
- Если используете CDN — предусмотрели invalidation.
Автоматизация:
- Желательно настроен CI/CD, чтобы не делать деплой руками.
- Все секреты (токены, ключи) хранятся в секретах платформы, а не в репозитории.
Заключение
Деплой на статический хостинг — это не просто «залить HTML на сервер», а понятный и повторяемый процесс: сборка проекта, подготовка правильной структуры файлов, настройка роутинга и кеширования, а затем автоматизация через CI/CD.
Статический хостинг выгоден тем, что:
- почти не требует администрирования сервера;
- хорошо масштабируется благодаря CDN;
- отлично подходит для фронтенд‑приложений с отдельным бэкендом.
Когда вы понимаете, какие именно файлы нужно публиковать, как работает SPA‑роутинг и как устроено кеширование, деплой превращается в рутинную, но надежную операцию, а не в набор «магических» действий.
Частозадаваемые технические вопросы
1. Как организовать разные окружения dev staging prod на статическом хостинге
Обычно создают несколько сайтов или доменов на одном и том же хостинге:
- dev.example.com — для разработки;
- staging.example.com — для тестирования;
- example.com — продакшен.
Для каждого окружения:
- Настраиваете отдельный сайт на хостинге (отдельный бакет S3, отдельный сайт в Netlify и т.д.).
- В CI прописываете разные переменные окружения (например, API URL) и разные команды деплоя.
- В коде используете переменные окружения, подставляемые при сборке, чтобы отличать конфиг для dev и prod.
2. Как сделать редирект с www на без www на статическом хостинге
Если у вас домен и поддомен указывают на один и тот же хостинг, редирект часто настраивается на уровне DNS‑провайдера или CDN:
- В CloudFront и Route 53 — через отдельный distribution для www, который делает redirect 301 на основной домен.
- В Netlify — через файл _redirects или netlify.toml с правилом:
- from: https://www.example.com/*
- to: https://example.com/:splat
- status: 301.
Главная идея — редирект происходит до того, как запрос дойдет до ваших статических файлов.
3. Как ограничить доступ к тестовому статическому сайту по паролю
Статический хостинг сам по себе не умеет аутентифицировать пользователей, но многие платформы добавляют такую фичу:
- В Netlify можно включить Basic Auth через конфигурацию в netlify.toml или через Enterprise‑функции.
- В S3 + CloudFront можно ограничить доступ по Signed URLs или Signed Cookies.
- В Nginx (если статику раздает он) — настроить Basic Auth в конфиге сервера.
Если платформа вообще не поддерживает ограничение доступа, можно:
- защитить сайт через VPN;
- или деплоить тестовую версию на приватный сервер с авторизацией.
4. Как подключить custom domain к статическому хостингу
Общая схема:
- В панели хостинга добавляете свой домен (example.com).
- Получаете подсказки по DNS (обычно нужно:
- A‑запись на IP хостинга, или
- CNAME на домен хостинга, типа site.netlify.app).
- В панели управления доменом (у регистратора или DNS‑провайдера) создаете записи так, как указал хостинг.
- Дожидаетесь обновления DNS (обычно до нескольких часов).
- Включаете HTTPS — чаще всего хостинг делает это автоматически через Let’s Encrypt.
5. Как деплоить статический сайт без Git только по файлам
Если у вас нет Git‑репозитория или вы не хотите завязываться на CI:
- Для S3 можно использовать aws s3 sync, как мы рассматривали.
- Для многих хостингов (например, Netlify) есть drag‑and‑drop — вы просто перетаскиваете папку с билдом в интерфейс.
- Можно использовать FTP или SFTP, если хостинг его поддерживает:
- Собираете проект локально.
- Подключаетесь по FTP‑клиенту.
- Загружаете содержимое папки билда в корень сайта.
- Удаляете старые файлы, если они больше не нужны.
В любом случае важно: на хостинг всегда грузите только результат сборки, а не исходники.
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

Vue 3 и Pinia
Антон Ларичев
TypeScript с нуля
Антон Ларичев