·3 min read·автор: Wedece Studio

Як суворий CSP перетворив статичний Next.js-сайт на SSR

Один заголовок безпеки може сповільнити сайт, хоча у видимому інтерфейсі не зміниться нічого. Ми зіткнулися з цим під час посилення захисту wedece.com. Content Security Policy із

Один заголовок безпеки може сповільнити сайт, хоча у видимому інтерфейсі не зміниться нічого. Ми зіткнулися з цим під час посилення захисту wedece.com. Content Security Policy із nonce здавалася очевидним покращенням, однак у нашій архітектурі Next.js непомітно перевела всі сторінки на рендеринг для кожного запиту. Виходом стала не відмова від CSP, а політика, що відповідає реальній моделі ризиків і платформі розміщення.

Як nonce змінив спосіб рендерингу

Перша реалізація створювала в middleware новий nonce для кожного запиту. Значення потрапляло до заголовка CSP, а спільний layout читав його із заголовків і передавав скриптам Next.js, аналітиці та структурованим даним. З погляду захисту це сильна схема: браузер виконує лише скрипти з непередбачуваним значенням, створеним саме для цієї відповіді.

Та nonce має архітектурну ціну. Next.js не може додати значення, яке з’явиться лише під час запиту, до HTML, згенерованого під час збірки. Тому офіційна документація вимагає динамічного рендерингу для nonce-based CSP. Оскільки заголовки читав layout, спільний для всього сайту, це торкнулося навіть повністю статичних сторінок. Пререндеринг і звичайне кешування CDN зникли, а кожен перегляд запускав серверний рендер React.

Чому це стало проблемою на Workers

wedece.com працює на Next.js через OpenNext у Cloudflare Workers. Безплатний план Workers дає 10 мс процесорного часу на HTTP-запит. У документації Cloudflare також зазначено, що важчі навантаження, зокрема server-side rendering, зазвичай потребують 10–20 мс. Це не означає, що SSR несумісний із Workers, але запас для маркетингового сайту, який рендерить кожну сторінку на кожен запит, стає надто малим.

У нашому випадку за помірної паралельності Worker повертав помилку 1102. Механізм безпеки був коректним, проте його вартість не вкладалася в обмеження цього розгортання. Головний висновок: CSP є частиною архітектури рендерингу, а не останнім рядком, який можна без наслідків додати перед релізом.

Політика, яку ми використовуємо зараз

Ми перенесли формування CSP із middleware до статичної конфігурації headers() у next.config.ts. Поточна політика залишає вузький default-src 'self', явно дозволяє потрібні домени аналітики та reCAPTCHA й містить обмеження object-src 'none', frame-ancestors 'none', base-uri 'self' та form-action 'self'. Застосунок більше не читає nonce із запиту, тому публічні сторінки знову можна пререндерити й кешувати.

Свідомий компроміс — unsafe-inline для скриптів і стилів. Це послаблює захист від ін’єкції inline-коду, отже не є загальною рекомендацією. Для нинішнього wedece.com ми прийняли його лише тому, що це інформаційний сайт без автентифікації, облікових записів і користувацького контенту, який відображається на сторінках. Якщо ця умова зміниться, політику доведеться переглянути.

Ми також не додаємо strict-dynamic. За правилами CSP Level 3, коли є чинний nonce або hash, ця директива змушує браузер ігнорувати списки дозволених хостів, 'self' та 'unsafe-inline' для скриптів. У статичній політиці вона не зробила б захист автоматично кращим, а змінила б модель довіри та могла б зламати зовнішні інтеграції.

Перевіряти треба відповідь сервера

Після змін ми перевіряємо не лише конфігурацію, а й реальну production-відповідь. 23 вересня 2026 року головна сторінка повертала активну статичну CSP разом із x-nextjs-prerender: 1 і cache-control: s-maxage=31536000. Playwright smoke-тести відкривають ключові маршрути та перевіряють блог, sitemap, RSS і структуровані дані. Окремий endpoint приймає звіти про порушення CSP і в production передає їх до Sentry.

Практичне правило просте: починайте з політики, якої потребує ваша модель загроз, а потім вимірюйте її вплив на рендеринг, кеш і зовнішні скрипти. Захист, що не витримує реального трафіку, ще не завершений; швидша конфігурація також не стає безпечною автоматично. Компроміс має бути явним, перевіреним і готовим до перегляду.

Сподобалось? Пишемо щокілька тижнів.

Підпишіться, щоб отримувати плейбуки студії, AI-інструменти та свіжі кейси.