In Quantum Metropolis, engineers constantly monitor and optimize the performance of all city systems - from energy grids to transportation networks. Every architectural decision impacts the speed and efficiency of the entire city. Similarly, in Next.js 15, choosing the right rendering strategy has a fundamental impact on application performance.
In this chapter, you will learn a detailed comparison of rendering strategies, performance metrics, and practical examples that will help you make informed architectural decisions.
How it works: Each user request causes the page to be rendered on the server in real-time.
1// app/dashboard/page.tsx
2// By default Next.js 15 uses cache, so we need to explicitly disable it
3export const dynamic = 'force-dynamic';
4// LUB
5// fetch(..., { cache: 'no-store' })
6
7async function getDashboardData() {
8 const res = await fetch('https://api.quantum.com/dashboard', {
9 cache: 'no-store' // Wymusza SSR
10 });
11 return res.json();
12}
13
14export default async function Dashboard() {
15 const data = await getDashboardData();
16
17 return (
18 <div>
19 <h1>Panel kontrolny</h1>
20 <p>Ostatnia aktualizacja: {new Date().toLocaleTimeString()}</p>
21 <DashboardContent data={data} />
22 </div>
23 );
24}Advantages:
Disadvantages:
How it works: Pages are generated once during build time and served as static HTML files.
1// app/blog/[slug]/page.tsx
2
3async function getPost(slug: string) {
4 const res = await fetch(`https://api.quantum.com/posts/${slug}`, {
5 cache: 'force-cache' // Wymusza SSG
6 });
7 return res.json();
8}
9
10export async function generateStaticParams() {
11 const posts = await fetch('https://api.quantum.com/posts').then(res => res.json());
12
13 return posts.map((post) => ({
14 slug: post.slug,
15 }));
16}
17
18export default async function BlogPost({
19 params,
20}: {
21 params: Promise<{ slug: string }>;
22}) {
23 const post = await getPost((await params).slug);
24
25 return (
26 <article>
27 <h1>{post.title}</h1>
28 <time>{post.publishedAt}</time>
29 <div dangerouslySetInnerHTML={{ __html: post.content }} />
30 </article>
31 );
32}Advantages:
Disadvantages:
How it works: Pages are generated statically but can be refreshed in the background after a specified time.
1// app/products/[id]/page.tsx
2
3async function getProduct(id: string) {
4 const res = await fetch(`https://api.quantum.com/products/${id}`, {
5 next: { revalidate: 3600 } // Revalidation every hour
6 });
7 return res.json();
8}
9
10export async function generateStaticParams() {
11 // Generuj tylko najpopularniejsze produkty podczas build
12 const popularProducts = await fetch('https://api.quantum.com/products/popular')
13 .then(res => res.json());
14
15 return popularProducts.map((product) => ({
16 id: product.id.toString(),
17 }));
18}
19
20export default async function ProductPage({
21 params,
22}: {
23 params: Promise<{ id: string }>;
24}) {
25 const product = await getProduct((await params).id);
26
27 return (
28 <div>
29 <h1>{product.name}</h1>
30 <p>Cena: {product.price} QC</p>
31 <p>Availability: {product.stock} units</p>
32 <button>Dodaj do koszyka</button>
33 </div>
34 );
35}
36
37export const revalidate = 3600; // Globalny czas rewalidacjiAdvantages:
Disadvantages:
TTFB is the time from sending a request to receiving the first byte of the response. In Metropolis Quantum, it's like the system's reaction time to a command - the faster, the better.
Benchmark (example values):
| Strategia | TTFB | Ocena | |-----------|------|-------| | SSG | 50-100ms | ⭐⭐⭐⭐⭐ Excellent | | ISR (cached) | 50-150ms | ⭐⭐⭐⭐ Bardzo dobry | | ISR (revalidating) | 200-500ms | ⭐⭐⭐ Dobry | | SSR | 300-800ms | ⭐⭐ Average | | SSR (slow API) | 1000ms+ | ⭐ Poor |
1// Measuring TTFB in the browser
2performance.getEntriesByType('navigation')[0].responseStart;LCP measures the time until the largest content element is displayed. It's like the speed at which the main hologram appears in Metropolis Quantum.
Benchmark:
| Strategia | LCP | Ocena | |-----------|-----|-------| | SSG | 0.5-1.5s | ⭐⭐⭐⭐⭐ Excellent | | ISR | 0.8-2s | ⭐⭐⭐⭐ Bardzo dobry | | SSR | 1.5-3s | ⭐⭐⭐ Dobry | | SSR (heavy) | 3s+ | ⭐⭐ Wymaga optymalizacji |
Rekomendacje Google:
1// Monitoring LCP
2import { onLCP } from 'web-vitals';
3
4onLCP((metric) => {
5 console.log('LCP:', metric.value);
6 // Send to analytics
7 analytics.track('Web Vitals', {
8 metric: 'LCP',
9 value: metric.value,
10 rating: metric.rating
11 });
12});CLS measures the visual stability of a page. It's like the stability of holographic interfaces in Metropolis Quantum - they should not "jump".
Benchmark:
| Strategia | CLS | Ocena | |-----------|-----|-------| | SSG | 0-0.05 | ⭐⭐⭐⭐⭐ Excellent | | ISR | 0-0.1 | ⭐⭐⭐⭐ Bardzo dobry | | SSR | 0.1-0.25 | ⭐⭐⭐ Good (if well optimized) |
Rekomendacje Google:
1// Monitoring CLS
2import { onCLS } from 'web-vitals';
3
4onCLS((metric) => {
5 console.log('CLS:', metric.value);
6 analytics.track('Web Vitals', {
7 metric: 'CLS',
8 value: metric.value,
9 rating: metric.rating
10 });
11});✅ Blog lub strona marketingowa
1// app/blog/[slug]/page.tsx - Idealny dla SSG
2export async function generateStaticParams() {
3 const posts = await getPosts();
4 return posts.map(post => ({ slug: post.slug }));
5}
6
7export default async function BlogPost({ params }) {
8 const post = await getPost((await params).slug);
9 return <Article post={post} />;
10}✅ Strona dokumentacji
✅ Landing pages
✅ E-commerce (product pages)
1// app/products/[id]/page.tsx - Idealny dla ISR
2export const revalidate = 300; // 5 minut
3
4export default async function Product({ params }) {
5 const product = await getProduct((await params).id);
6
7 return (
8 <div>
9 <h1>{product.name}</h1>
10 <p>Cena: {product.price}</p>
11 <p>W magazynie: {product.stock}</p>
12 </div>
13 );
14}✅ Newsroom / Media
✅ User profiles (public)
✅ Dashboard / Panel administracyjny
1// app/dashboard/page.tsx - Idealny dla SSR
2export const dynamic = 'force-dynamic';
3
4export default async function Dashboard() {
5 const session = await getServerSession();
6 const userData = await getUserData(session.userId);
7
8 return (
9 <div>
10 <h1>Witaj, {userData.name}</h1>
11 <RealTimeAnalytics userId={session.userId} />
12 </div>
13 );
14}✅ Search results
✅ User-specific content
1// app/blog/[slug]/page.tsx
2import { Metadata } from 'next';
3
4export async function generateMetadata({ params }): Promise<Metadata> {
5 const post = await getPost((await params).slug);
6
7 return {
8 title: post.title,
9 description: post.excerpt,
10 openGraph: {
11 images: [post.coverImage],
12 },
13 };
14}
15
16export async function generateStaticParams() {
17 const posts = await getAllPosts();
18
19 return posts.map((post) => ({
20 slug: post.slug,
21 }));
22}
23
24async function getPost(slug: string) {
25 const res = await fetch(`https://cms.quantum.com/posts/${slug}`, {
26 cache: 'force-cache'
27 });
28 return res.json();
29}
30
31export default async function BlogPost({ params }) {
32 const post = await getPost((await params).slug);
33
34 return (
35 <article>
36 <h1>{post.title}</h1>
37 <time>{post.publishedAt}</time>
38 <Image
39 src={post.coverImage}
40 alt={post.title}
41 width={1200}
42 height={630}
43 priority
44 />
45 <div dangerouslySetInnerHTML={{ __html: post.content }} />
46 </article>
47 );
48}Metrics:
1// app/products/[id]/page.tsx
2
3async function getProduct(id: string) {
4 const res = await fetch(`https://api.quantum-shop.com/products/${id}`, {
5 next: { revalidate: 600 } // Rewalidacja co 10 minut
6 });
7
8 if (!res.ok) {
9 notFound();
10 }
11
12 return res.json();
13}
14
15export async function generateStaticParams() {
16 // Generate only top 100 products during build
17 const topProducts = await fetch('https://api.quantum-shop.com/products/top?limit=100')
18 .then(res => res.json());
19
20 return topProducts.map((product) => ({
21 id: product.id.toString(),
22 }));
23}
24
25export default async function ProductPage({ params }) {
26 const product = await getProduct((await params).id);
27
28 return (
29 <div className="product-page">
30 <ProductGallery images={product.images} />
31
32 <div className="product-info">
33 <h1>{product.name}</h1>
34
35 <div className="price-stock">
36 <span className="price">{product.price} QC</span>
37 <StockIndicator stock={product.stock} />
38 </div>
39
40 <div className="description">
41 {product.description}
42 </div>
43
44 <AddToCartButton productId={product.id} />
45
46 <ProductReviews productId={product.id} />
47 </div>
48
49 <RelatedProducts categoryId={product.categoryId} />
50 </div>
51 );
52}
53
54export const revalidate = 600; // 10 minutMetrics:
1// app/dashboard/analytics/page.tsx
2
3export const dynamic = 'force-dynamic';
4export const fetchCache = 'force-no-store';
5
6async function getRealTimeAnalytics(userId: string) {
7 const res = await fetch(`https://api.quantum.com/analytics/${userId}`, {
8 cache: 'no-store',
9 headers: {
10 Authorization: `Bearer ${process.env.API_TOKEN}`
11 }
12 });
13
14 return res.json();
15}
16
17export default async function AnalyticsDashboard() {
18 const session = await getServerSession();
19
20 if (!session) {
21 redirect('/login');
22 }
23
24 const analytics = await getRealTimeAnalytics(session.user.id);
25
26 return (
27 <div className="analytics-dashboard">
28 <h1>Analityka w czasie rzeczywistym</h1>
29
30 <div className="stats-grid">
31 <StatCard
32 title="Active Users"
33 value={analytics.activeUsers}
34 change={analytics.activeUsersChange}
35 />
36
37 <StatCard
38 title="Przychody dzisiaj"
39 value={`${analytics.todayRevenue} QC`}
40 change={analytics.revenueChange}
41 />
42
43 <StatCard
44 title="Konwersje"
45 value={`${analytics.conversionRate}%`}
46 change={analytics.conversionChange}
47 />
48 </div>
49
50 <RevenueChart data={analytics.revenueChart} />
51 <UserActivityFeed data={analytics.recentActivity} />
52 </div>
53 );
54}Metrics:
1// app/news/page.tsx
2
3async function getLatestNews() {
4 const res = await fetch('https://api.quantum-news.com/latest', {
5 next: { revalidate: 60 } // Revalidation every minute
6 });
7
8 return res.json();
9}
10
11export default async function NewsFeed() {
12 const initialNews = await getLatestNews();
13
14 return (
15 <div className="news-feed">
16 <h1>News from Metropolis Quantum</h1>
17
18 {/* Server-rendered initial content (ISR) */}
19 <NewsList initialNews={initialNews} />
20
21 {/* Client-side updates co 30 sekund */}
22 <ClientNewsUpdater />
23 </div>
24 );
25}
26
27export const revalidate = 60;1// components/ClientNewsUpdater.tsx
2'use client';
3
4import { useEffect, useState } from 'react';
5
6export function ClientNewsUpdater() {
7 const [latestNews, setLatestNews] = useState([]);
8
9 useEffect(() => {
10 const interval = setInterval(async () => {
11 const res = await fetch('/api/news/latest');
12 const data = await res.json();
13 setLatestNews(data);
14 }, 30000); // 30 sekund
15
16 return () => clearInterval(interval);
17 }, []);
18
19 if (latestNews.length === 0) return null;
20
21 return (
22 <div className="breaking-news">
23 <h2>Latest News</h2>
24 {latestNews.map(news => (
25 <NewsCard key={news.id} news={news} />
26 ))}
27 </div>
28 );
29}Metrics:
1// app/layout.tsx
2import { SpeedInsights } from '@vercel/speed-insights/next';
3import { Analytics } from '@vercel/analytics/react';
4
5export default function RootLayout({ children }) {
6 return (
7 <html lang="pl">
8 <body>
9 {children}
10 <SpeedInsights />
11 <Analytics />
12 </body>
13 </html>
14 );
15}1// app/web-vitals.tsx
2'use client';
3
4import { useReportWebVitals } from 'next/web-vitals';
5
6export function WebVitals() {
7 useReportWebVitals((metric) => {
8 // Sending to your own analytics
9 const body = JSON.stringify({
10 name: metric.name,
11 value: metric.value,
12 rating: metric.rating,
13 delta: metric.delta,
14 id: metric.id,
15 });
16
17 fetch('/api/analytics/web-vitals', {
18 method: 'POST',
19 headers: { 'Content-Type': 'application/json' },
20 body,
21 });
22 });
23
24 return null;
25}1# .github/workflows/lighthouse.yml
2name: Lighthouse CI
3
4on:
5 pull_request:
6 branches: [main]
7
8jobs:
9 lighthouse:
10 runs-on: ubuntu-latest
11 steps:
12 - uses: actions/checkout@v3
13 - uses: actions/setup-node@v3
14
15 - name: Install dependencies
16 run: npm ci
17
18 - name: Build
19 run: npm run build
20
21 - name: Run Lighthouse CI
22 run: |
23 npm install -g @lhci/cli
24 lhci autorun
25
26 - name: Upload results
27 uses: actions/upload-artifact@v3
28 with:
29 name: lighthouse-results
30 path: .lighthouseci1// next.config.js
2module.exports = {
3 images: {
4 formats: ['image/avif', 'image/webp'],
5 deviceSizes: [640, 750, 828, 1080, 1200],
6 },
7 compress: true,
8 poweredByHeader: false,
9 generateEtags: true,
10};1// Use on-demand revalidation for important updates
2// app/api/revalidate/route.ts
3import { revalidatePath } from 'next/cache';
4import { NextRequest } from 'next/server';
5
6export async function POST(request: NextRequest) {
7 const secret = request.nextUrl.searchParams.get('secret');
8
9 if (secret !== process.env.REVALIDATION_TOKEN) {
10 return Response.json({ message: 'Invalid token' }, { status: 401 });
11 }
12
13 const path = request.nextUrl.searchParams.get('path');
14
15 if (path) {
16 revalidatePath(path);
17 return Response.json({ revalidated: true });
18 }
19
20 return Response.json({ message: 'Missing path' }, { status: 400 });
21}1// Use streaming for faster TTFB
2// app/dashboard/page.tsx
3import { Suspense } from 'react';
4
5export default function Dashboard() {
6 return (
7 <div>
8 <h1>Dashboard</h1>
9
10 <Suspense fallback={<AnalyticsSkeleton />}>
11 <Analytics />
12 </Suspense>
13
14 <Suspense fallback={<ChartSkeleton />}>
15 <RevenueChart />
16 </Suspense>
17 </div>
18 );
19}1Must data be real-time?
2├─ TAK → SSR
3│ └─ Consider streaming and Suspense for better performance
4│
5└─ NIE
6 │
7 ├─ Does data change more often than once per hour?
8 │ ├─ TAK → ISR (revalidate: 60-3600)
9 │ │ └─ Consider on-demand revalidation for important updates
10 │ │
11 │ └─ NIE → SSG
12 │ └─ Use generateStaticParams for all possible paths
13 │
14 └─ Czy potrzebujesz personalizacji?
15 ├─ TAK → SSR lub ISR + Client Components
16 └─ NIE → SSG lub ISRKluczowe wnioski:
Just as Quantum Metropolis engineers optimize every system for maximum performance, you too should consciously choose the rendering strategy for each part of your application. There is no single "best" solution - the key is matching the strategy to the specific use case.
Monitor Web Vitals, test different approaches, and always put user experience first. In the era of quantum technologies, every millisecond matters!