JAMstack на практике: как мы перестроили новостной сайт на Gatsby и выжили при DDoS-нагрузке
У клиента был региональный новостной сайт на WordPress. Работал нормально - до тех пор пока одна статья не попала в топ агрегаторов новостей. 50 000 одновременных посетителей. Сервер упал за 3 минуты.
Не потому что железо слабое - они уже переплачивали за сервер. Проблема в архитектуре: WordPress при каждом запросе генерирует страницу через PHP + MySQL. Кэш помогает, но только если настроен идеально. При внезапном пике трафика кэш «промахивается», и база падает под лавиной запросов.
Решение: убрать сервер из цепочки обработки запроса вообще.
Что такое JAMstack
JAM = JavaScript, APIs, Markup.
Суть: если контент не меняется от запроса к запросу - собери HTML один раз при деплое и раздавай со CDN. Нет PHP, нет базы данных, нет сервера в момент запроса. CDN-нода в Москве или Алматы отдаёт заранее собранный HTML за 15-20ms независимо от количества одновременных пользователей.
Это не новая идея - так работал весь интернет в 1995 году. JAMstack - это современный инструментарий для той же концепции.
Где не работает: персонализированный контент, корзина покупок, авторизованные страницы, реальное время. Для этого - клиентский JavaScript, вызывающий API.
Стек
- Gatsby 2.x - генератор статических сайтов на React. При сборке получает данные, рендерит страницы, отдаёт HTML/CSS/JS.
- Contentful - headless CMS. Редакторы работают в удобном интерфейсе, разработчики получают данные через API.
- Netlify - CI/CD + CDN. Push в GitHub → автоматический билд → деплой на глобальную CDN за ~3 минуты.
- Netlify Functions - serverless функции для динамических частей.
Headless CMS: разделение контента и представления
Традиционный WordPress: хранение контента + редакторский интерфейс + PHP-шаблоны - всё в одном. «Голова» (фронтенд) намертво прикручена к бэкенду.
Headless CMS - только хранение и редактирование. API отдаёт контент куда угодно: сайт на Gatsby, мобильное приложение, email-рассылка.
Редакторы работали в чистом интерфейсе Contentful. Опубликовал статью - Contentful вызвал webhook → Netlify запустил сборку → через 45 секунд статья на сайте.
Gatsby: как работает статическая генерация
// gatsby-node.js - программно создаём страницы при сборке
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
// Этот GraphQL-запрос выполняется ПРИ СБОРКЕ, не при запросе пользователя
const result = await graphql(`
{
allContentfulArticle {
nodes {
slug
updatedAt
}
}
}
`);
result.data.allContentfulArticle.nodes.forEach(article => {
createPage({
path: `/news/${article.slug}`,
component: path.resolve('./src/templates/article.js'),
context: { slug: article.slug },
});
});
};
// src/templates/article.js - шаблон страницы
import { graphql } from 'gatsby';
// Этот запрос тоже выполняется при сборке
export const query = graphql`
query ArticleQuery($slug: String!) {
contentfulArticle(slug: { eq: $slug }) {
title
publishedAt
body { raw }
heroImage { file { url } }
author { name }
}
}
`;
export default function ArticlePage({ data }) {
const { title, publishedAt, body, heroImage } = data.contentfulArticle;
return (
<article>
<h1>{title}</h1>
<time>{publishedAt}</time>
<img src={heroImage.file.url} alt={title} />
<RichText content={body} />
</article>
);
}
1200 статей = 1200 HTML-файлов, собранных один раз. CDN раздаёт их без участия PHP или базы данных.
Оптимизация времени сборки
Первые сборки занимали 14 минут. Неприемлемо для редакции.
Проблема 1: обработка изображений локально. Gatsby-image скачивал и ресайзил каждое изображение. 800 картинок × 6 форматов = 4800 операций.
Решение: трансформации через CDN Contentful:
// Вместо локальной обработки - параметры в URL изображения
// Contentful ресайзит на своей CDN, билд не тратит время
const imageUrl = `${asset.url}?w=800&h=600&fit=fill&fm=webp&q=80`;
Проблема 2: нет кэша между сборками. Gatsby 2.5+ добавил постоянный кэш на диске. Если статья не изменилась - её страница не пересобирается.
После оптимизации:
- Полная пересборка: 95 секунд
- Инкрементальная (одна изменённая статья): 42 секунды
Deploy previews изменили процесс
Каждый pull request получал автоматически собранную preview-версию сайта по уникальному URL. Редакторы и клиент могли проверить изменения до мержа. Никакого staging-сервера, никаких «задеплой на тест».
# netlify.toml
[build]
command = "gatsby build"
publish = "public"
[[headers]]
for = "/static/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
# Статика с хэшами в имени файла - кэшируем на год
[[headers]]
for = "/*.html"
[headers.values]
Cache-Control = "public, max-age=0, must-revalidate"
# HTML-файлы - всегда свежие с CDN
Результаты
| Метрика | WordPress | Gatsby + Contentful |
|---|---|---|
| Time to First Byte | 350ms (с кэшем), 1.9s (без) | 18ms (CDN edge) |
| 50 000 одновременных посетителей | Падение сервера | Нет влияния |
| Стоимость хостинга | €45/мес (VPS + MySQL) | €0 (Netlify free) + $39/мес Contentful |
| Публикация статьи | Мгновенно | 42 секунды |
| PageSpeed mobile | 54 | 97 |
Когда JAMstack не подходит
Следующий проект был интернет-магазин: 80 000 товаров, реальные остатки, персонализированные цены. Начали с Gatsby - полные сборки занимали 30 минут. Отказались.
Для больших каталогов с частыми изменениями - Next.js с ISR (Incremental Static Regeneration): страницы собираются по запросу и кэшируются, а не все заранее.
JAMstack хорошо работает для контентных сайтов с сотнями-тысячами страниц, обновляемых несколько раз в день. Для e-commerce с динамическим инвентарём - смотрите в сторону ISR или SSR.