Architect a hybrid Next.js app using route segmentation, with SSG for public content (CDN-cached) and SSR for personalized routes, sharing common components and data fetching logic through a monorepo structure
Building a Next.js application that handles both high-scale cacheable content and personalized user-specific pages requires a deliberate architectural split. The key insight is that these different page types have opposing infrastructure needs—SSG pages benefit from CDN distribution and zero origin load, while SSR pages require compute per request and often need to access user context. By clearly separating these concerns at the routing level, implementing appropriate caching strategies for each, and sharing common code through a well-organized monorepo, you can build a system that scales efficiently while providing personalized experiences where needed.
Route segmentation: Use route groups to separate public/marketing pages (SSG/ISR) from authenticated/app pages (SSR) at the folder level, allowing different caching strategies per segment.
Shared component library: Maintain a common UI component library for elements like headers, footers, and cards that appear in both sections, ensuring design consistency.
Data layer abstraction: Create a unified data fetching layer that can work in both static (build-time) and dynamic (request-time) contexts, with appropriate caching directives.
Authentication boundary: Place authentication checks in middleware or layout components that only apply to protected routes, keeping public routes completely cacheable.
CDN strategy: Configure CDN to cache public routes aggressively (hours/days) while bypassing cache for personalized routes or setting very short TTLs.
For marketing routes (SSG/ISR), implement aggressive CDN caching with Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400. This ensures pages are served from edge locations for an hour, with background revalidation. For product pages or blog posts, use ISR with on-demand revalidation via webhooks when content changes. For authenticated routes, use SSR with minimal caching—perhaps 1-5 minutes at the CDN level if the content isn't highly sensitive, or private, no-cache for truly personalized data. The key is matching the caching strategy to the content's personalization level and update frequency.
Create a unified data layer that works in both static and dynamic contexts: functions that accept options for caching strategy, revalidation, and tags.
For static contexts (build time), use force-cache to avoid repeated API calls across pages.
For dynamic contexts (request time), allow passing of user context and use no-store when needed.
Implement tags for on-demand revalidation that work across both static and dynamic pages.
Use the same data functions in both marketing pages (ISR) and authenticated pages (SSR) by passing appropriate options.
Handle authentication at the middleware level for route protection, but defer user data fetching to the page/layout level. This keeps middleware fast (edge runtime) while allowing personalized pages to access full user context. For public routes, middleware simply passes through. For protected routes, middleware verifies the session and redirects if needed. The actual user data can then be fetched in the layout or page using a server-side auth utility that reads the session cookie. This separation ensures public routes remain fully cacheable while protected routes can access user context.
Use monorepo tooling (Turborepo, Nx) to manage shared packages across marketing and app sections, enabling efficient builds and dependency management.
Configure separate build pipelines for marketing and app sections if they have different update frequencies—marketing might rebuild daily, while app deploys per commit.
Deploy to platforms with good ISR support like Vercel, which handles the complexity of background regeneration and cache invalidation across both static and dynamic routes.
Set up on-demand revalidation webhooks for marketing content that trigger only when CMS content changes, avoiding unnecessary rebuilds.
Monitor cache hit rates and SSR response times separately for each route segment to identify bottlenecks.
Consider an e-commerce platform with both public product pages (need SEO, cacheable) and user dashboards (personalized, dynamic). Product pages use ISR with on-demand revalidation when inventory or prices change—served from CDN edge, 50ms TTFB, 99.9% cache hit rate. User dashboards use SSR with user-specific data, served from origin with 200ms TTFB but personalized content. Both share the same product data layer, checkout components, and UI library through a monorepo. The result: marketing pages scale infinitely with zero origin load during traffic spikes, while authenticated sections provide personalized experiences with predictable server costs.