Однажды меня попросили сделать перенос старого SPA (Single Page Application)-приложения на новый S3 bucket в CloudFront.
Но не просто перенос, а хитрый: часть путей должна идти на новое приложение, а часть - оставаться на старом.
Legacy страницы типа /welcome, /login-start, /login-end пока не переписаны в новом коде, их надо сохранить.
А всё остальное - на свежий
app-v2.По сути частичная миграция на новое приложение, не ломая старое.
Казалось бы, что может пойти не так? 😁
Задача звучит просто:
-
/* > новый bucket (app-v2)-
/welcome*, /login-start*, /login-end*, /help-bot* > старый bucket-
/assets/*, /sdk.js > старый bucketОткрываю Terraform, добавляю новый origin для app-v2 bucket.
Меняю
default_cache_behavior на новый origin.Добавляю
ordered_cache_behavior для legacy путей на старый bucket.Terraform plan - всё как задумано.
Apply. Жду. Готово.
Иду проверять.
- https://stage-app.example.com/ - работает, новое приложение грузится. ✅
- https://stage-app.example.com/?appId=xxx - работает, редиректит. ✅
- https://stage-app.example.com/welcome - ...
"Server Error. An unexpected error happened."
Чо. 🤡
Первая мысль - кэш CloudFront. Делаю invalidation. Жду. Проверяю.
Та же херня.
Вторая мысль - может origin неправильный?
Проверяю напрямую S3 bucket:
curl -I "http://stage-app.example.com.s3-website-us-east-1.amazonaws.com/index.html"
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 4755
Bucket работает. index.html есть. 4755 байт. Всё ок.
Третья мысль - может
custom_error_response мешает?В CloudFront есть глобальный обработчик 404 ошибок, который возвращает
/index.html.Думаю: "А, наверное S3 возвращает 404, а CloudFront берёт index.html с дефолтного origin!"
Убираю
custom_error_response. Apply. Проверяю.Та же херня. Даже хуже - теперь вместо "Server Error" просто "Not found".
Возвращаю
custom_error_response. Сижу, чешу репу.Ладно, поехали курлить по-взрослому.
curl -sI "https://stage-app.example.com/welcome"
HTTP/2 200
content-type: text/html
x-cache: Hit from cloudfront
Стоп. 200? Не 404? CloudFront отдаёт 200 и HTML?
Смотрю body:
curl -s "https://stage-app.example.com/welcome" | head -20
Да, это HTML старого приложения.
webpackJsonstatham@app/web. Всё верно.Так почему "Server Error" в браузере?
Если HTML грузится, значит проблема в JavaScript.
Приложение падает после загрузки.
Открываю DevTools > Network.
И тут я вижу ЭТО:
/static/js/main.a1b2c3d4.chunk.js → content-type: text/html ❌
/static/css/main.e5f6g7h8.chunk.css → content-type: text/html ❌
JS файлы возвращают HTML вместо JavaScript.
Браузер пытается выполнить HTML как JavaScript.
Приложение падает с "Server Error".
Сука. 😁
Проверяю напрямую S3:
curl -sI "http://stage-app.example.com.s3-website-us-east-1.amazonaws.com/static/js/main.a1b2c3d4.chunk.js"
HTTP/1.1 200 OK
Content-Type: text/javascript
Content-Length: 1080675
S3 отдаёт правильно! 1MB JavaScript!
А через CloudFront:
curl -sI "https://stage-app.example.com/static/js/main.a1b2c3d4.chunk.js"
HTTP/2 200
content-type: text/html
x-cache: Error from cloudfront
CloudFront отдаёт HTML. И
x-cache: Error from cloudfront. Красота.Теперь понятно что происходит.
Сажусь рисовать на планшете путь запроса (а я всегда рисую).
Проблема:
1. Браузер >
/welcome2. CloudFront матчит
/welcome* > origin: OLD bucket3. S3 не находит файл
/welcome (это SPA route, не файл)4. S3 возвращает
error_document = index.html5. Браузер получает HTML старого приложения ✅
6. HTML содержит:
<script src="./static/js/main.a1b2c3d4.chunk.js">7. Браузер резолвит
./static/... относительно /welcome8. Браузер запрашивает
/static/js/main.a1b2c3d4.chunk.js9. CloudFront матчит
/* (default) > origin: app-v2 bucket ❌10. В app-v2 bucket НЕТ файла main.a1b2c3d4.chunk.js!
11. S3 возвращает 404
12. CloudFront custom_error_response > /index.html
13. Браузер получает index.html (HTML) вместо JavaScript
14. JavaScript парсер: "че за херня, это не JS"
15. Приложение: "Server Error"🐒