Вот классический сценарий: вы добавляете Content-Security-Policy в Next.js-приложение, проверяете сборку в production — всё работает. Запускаете next dev, страница рендерится, но все клиентские фичи молча умирают. Поиск не фильтрует, кнопки не кликаются, hydration не происходит. При этом в консоли нет явных ошибок, только неприметное сообщение про unsafe-eval. Разберёмся, почему так происходит и как это фиксить.
Почему dev и production ведут себя по-разному
Вот что происходит под капотом:
- В development-режиме Next.js использует
evalдля динамического выполнения кода модулей. Это нужно для React Fast Refresh и HMR — чтобы подменять компоненты без полной перезагрузки страницы. - В production-сборке код компилируется в статические бандлы, и
evalне требуется.
Если в CSP нет 'unsafe-eval', браузер блокирует выполнение кода в dev-режиме, но:
- Страница всё равно рендерится (это SSR)
- Hydration падает молча, без красного overlay Next.js
- В консоли ошибка без stack trace, её легко пропустить
// Пример CSP, который сломает next dev
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: "script-src 'self' 'unsafe-inline'", // нет unsafe-eval
},
]
Два рабочих решения
Вариант A: Динамический CSP для development
Добавляем 'unsafe-eval' только в dev-режиме:
const isDev = process.env.NODE_ENV === 'development'
const csp = [
"default-src 'self'",
`script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ''}`,
`connect-src 'self'${isDev ? ' ws: wss:' : ''}`,
// остальные директивы
].join('; ')
// В next.config.mjs
async headers() {
return [{
source: '/:path*',
headers: [{ key: 'Content-Security-Policy', value: csp }],
}]
}
Важно: оставьте комментарий, почему тут есть условие. Без объяснения это выглядит как security hole, и следующий разработчик может “починить” код, удалив проверку.
Вариант B: Тестировать только на production-сборке
Альтернативный подход — вообще не полагаться на next dev для проверки клиентской логики:
next build && next start
Плюсы:
- Тестируете ровно ту среду, которая будет в production
- Не нужно усложнять CSP
Минусы:
- Нет Fast Refresh — приходится ждать пересборки после каждого изменения
- Медленнее итерации
Этот вариант особенно актуален, если production-среда существенно отличается от dev (например, когда приложение запускается в edge-функциях).
Что ещё может сломаться в dev из-за CSP
'unsafe-eval' — не единственная директива, которая ведёт себя по-разному в dev/prod:
| Директива | Что нужно в dev | Почему |
|---|---|---|
script-src | 'unsafe-eval' | Для работы React Refresh |
connect-src | ws: wss: | Для HMR WebSocket |
style-src | 'unsafe-inline' | Если dev-сервер инжектит CSS |
Как не попадать в эту ловушку
Простой чек-лист для Next.js + CSP:
- После добавления CSP запустите
next dev - Откройте страницу
- Попробуйте кликнуть любую интерактивную кнопку
- Проверьте консоль — именно первую ошибку, не последнюю
Это займёт 30 секунд, но покажет разницу между “страница рендерится” и “приложение работает”.
Если используете AI coding tools вроде GitHub Copilot или Cursor, они могут не знать про эту особенность Next.js — код будет выглядеть корректным, но не работать в dev. В таких случаях ручное тестирование критично.
Главный вывод: CSP в Next.js нужно тестировать в обоих режимах, потому что runtime-окружение dev-сервера существенно отличается от production. И да, иногда unsafe-eval — это не баг, а фича.
Попробуй сам: Cursor — AI-редактор для разработчиков.