Next.js 16 + Spring Boot - nasza produkcyjna architektura
Frontend w Next.js App Router, backend w Spring Boot, panel admin w SPA. Pokazuję realny układ z apps/site: dlaczego nie monolit i jak to się deployuje.
Frontend w Next.js App Router, backend w Spring Boot, panel admin w SPA. Pokazuję realny układ z apps/site: dlaczego nie monolit i jak to się deployuje.
Większość projektów, w których uczestniczę, kończy z tym samym układem: statyczny frontend w Next.js + JVM-owy backend w Spring Boot + SPA admin. To nie jest pierwsza decyzja - to wynik kilku iteracji. Tutaj opisuję, co działa i co bym zmienił, gdybym zaczynał dziś.
Repozytorium itsolutions-platform jest pnpm-workspacem z trzema apkami:
apps/site - strona publiczna. Next.js 16 z output: 'export'.apps/site/admin-frontend - admin SPA (React Router, Vite). Embedded w site.apps/site/backend - Spring Boot 3, PostgreSQL, JWT auth.Nie jest to wybór ideologiczny. To trzy zupełnie różne use case'y:
| Apka | Charakterystyka ruchu | Decyzja techniczna |
|---|---|---|
| Public site | Long-tail SEO, 95% gości raz | Static export → Caddy → CDN |
| Admin | 1 użytkownik, 100 wejść/dzień | SPA bez SSR - szybki dev cycle |
| Backend | Stałe TPS, transakcje, e-maile | JVM + JPA + scheduler |
Każdy z tych komponentów ma inne wymagania na uptime, cold start, deploy frequency. Łączenie ich w jeden monolit Next.js znaczyłoby kompromis wszędzie po trochu.
Pytanie zadawane co tydzień. Powody, dla których trzymam się JVM-a:
output: 'export' wyklucza serwerowe rzeczy. Strona ma być serwowana
przez Caddy z CDN - żaden Node runtime nie odpala się przy każdym request.
Lead z formularza idzie POST-em do api.itsolutionsdz.pl, nie do Next.js.@Scheduled,
Spring Mail, Spring Retry. Nie chcę w Vercel CRON-ach robić retry-logiki
ręcznie.Admin SPA jest embedded w site:
apps/site/admin-frontend/ ← React Router, Vite
apps/site/public/admin/ ← build output, kopiowany przy `pnpm build`
apps/site/middleware.ts ← przepuszcza /admin/* przez basic-auth
Po zalogowaniu admin trzyma JWT w httpOnly cookie. Wszystkie requesty
do backendu lecą przez fetch('/api/...') - Caddy proxuje ten prefix
do Spring Boot na innym porcie. Cookie ma SameSite=Lax, więc CSRF wymaga
osobnego tokenu, ale dzięki temu admin może być na tej samej domenie
co strona publiczna bez CORS-owej gimnastyki.
Stack żyje na jednym VPS:
Deploy to docker compose -f docker-compose.prod.yml up -d --build.
Static out/ jest serwowane przez Caddy bez Node-a w środku.
Backend i Postgres w kontenerach z restart-policy.
“Architektura ma być wynikiem wymagań projektowych, nie listy modnych narzędzi z konferencji.
”
Trzy apki w monorepo, każda z innym deploy targetem, sprawiają, że mogę zmieniać hero section na stronie bez deployu backendu. Tego nie zrobiłbym w Next.js full-stack.