We use cookies to enhance your experience on the site
CodeWorlds

Performance Benchmarks in Next.js 15 - SSR vs SSG vs ISR

In the Quantum Metropolis of 2150, system performance is a matter of survival. Every millisecond of delay can mean data loss in the quantum network or failure of critical systems. That's why Metropolis engineers must thoroughly understand the performance differences between rendering strategies in Next.js 15 and be able to choose the optimal solution for each case.

What Are Performance Benchmarks?

Benchmarks are performance measurements of an application under controlled conditions. They allow comparing different rendering strategies against key metrics such as:

  • TTFB (Time to First Byte) - time from sending a request to receiving the first byte of the response
  • FCP (First Contentful Paint) - time until the first content is rendered on screen
  • LCP (Largest Contentful Paint) - time until the largest element in the viewport is rendered
  • TTI (Time to Interactive) - time until the page is fully interactive
  • CLS (Cumulative Layout Shift) - visual stability of the page during loading

Comparing Rendering Strategies

SSG (Static Site Generation) - The Fastest Option

SSG generates pages at application build time. The resulting HTML files are served directly from a CDN, ensuring the lowest possible latency.

1// app/blog/[slug]/page.tsx - SSG in Next.js 15
2import { notFound } from 'next/navigation';
3
4// Generating static paths at build time
5export async function generateStaticParams() {
6  const posts = await fetch('https://api.example.com/posts').then(r => r.json());
7  return posts.map((post: { slug: string }) => ({ slug: post.slug }));
8}
9
10// Default caching = SSG
11async function getPost(slug: string) {
12  const res = await fetch(\`https://api.example.com/posts/\${slug}\`);
13  if (!res.ok) return null;
14  return res.json();
15}
16
17export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
18  const post = await getPost((await params).slug);
19  if (!post) notFound();
20
21  return (
22    <article>
23      <h1>{post.title}</h1>
24      <p>{post.content}</p>
25    </article>
26  );
27}

Typical SSG Metrics:

| Metric | Value | Rating | |--------|-------|--------| | TTFB | 5-50ms | Excellent | | FCP | 100-300ms | Excellent | | LCP | 200-500ms | Excellent | | TTI | 300-800ms | Very good | | CLS | 0-0.05 | Excellent |

SSR (Server-Side Rendering) - Dynamic Content

SSR renders pages on the server for every request. It is slower than SSG but ensures always up-to-date data.

1// app/dashboard/page.tsx - SSR in Next.js 15
2import { cookies } from 'next/headers';
3
4// Forcing dynamic rendering
5export const dynamic = 'force-dynamic';
6
7async function getDashboardData(token: string) {
8  const res = await fetch('https://api.example.com/dashboard', {
9    headers: { Authorization: \`Bearer \${token}\` },
10    cache: 'no-store', // No caching = SSR
11  });
12  return res.json();
13}
14
15export default async function Dashboard() {
16  const cookieStore = await cookies();
17  const token = cookieStore.get('auth-token')?.value || '';
18  const data = await getDashboardData(token);
19
20  return (
21    <div>
22      <h1>User Dashboard</h1>
23      <p>Balance: {data.balance} QC</p>
24      <p>Last activity: {data.lastActivity}</p>
25    </div>
26  );
27}

Typical SSR Metrics:

| Metric | Value | Rating | |--------|-------|--------| | TTFB | 100-500ms | Average | | FCP | 300-800ms | Good | | LCP | 500-1200ms | Average | | TTI | 500-1500ms | Average | | CLS | 0-0.1 | Good |

ISR (Incremental Static Regeneration) - The Compromise

ISR combines the advantages of SSG (speed) with SSR (data freshness). Pages are generated statically but refreshed in the background after a specified time period.

1// app/products/page.tsx - ISR in Next.js 15
2async function getProducts() {
3  const res = await fetch('https://api.example.com/products', {
4    next: { revalidate: 60 }, // Revalidation every 60 seconds
5  });
6  return res.json();
7}
8
9export default async function ProductsPage() {
10  const products = await getProducts();
11
12  return (
13    <div>
14      <h1>Quantum Store Products</h1>
15      <div className="grid">
16        {products.map((product: any) => (
17          <div key={product.id}>
18            <h2>{product.name}</h2>
19            <p>Price: {product.price} QC</p>
20          </div>
21        ))}
22      </div>
23    </div>
24  );
25}

Typical ISR Metrics:

| Metric | Value (cache hit) | Value (revalidation) | Rating | |--------|-------------------|----------------------|--------| | TTFB | 5-50ms | 100-400ms | Very good | | FCP | 100-300ms | 300-700ms | Very good | | LCP | 200-500ms | 500-1000ms | Good | | TTI | 300-800ms | 500-1200ms | Good | | CLS | 0-0.05 | 0-0.05 | Excellent |

On-Demand Revalidation in Next.js 15

Next.js 15 introduces improved on-demand revalidation that allows refreshing the cache without waiting for the revalidation time to expire:

1// app/api/revalidate/route.ts
2import { revalidatePath, revalidateTag } from 'next/cache';
3import { NextRequest, NextResponse } from 'next/server';
4
5export async function POST(request: NextRequest) {
6  const { path, tag, secret } = await request.json();
7
8  // Security token verification
9  if (secret !== process.env.REVALIDATION_SECRET) {
10    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
11  }
12
13  if (tag) {
14    // Tag-based revalidation - refreshes all fetches with the given tag
15    revalidateTag(tag);
16    return NextResponse.json({ revalidated: true, tag });
17  }
18
19  if (path) {
20    // Path-based revalidation - refreshes a specific page
21    revalidatePath(path);
22    return NextResponse.json({ revalidated: true, path });
23  }
24
25  return NextResponse.json({ error: 'Missing path or tag' }, { status: 400 });
26}
27
28// Using tags in fetch
29async function getProduct(id: string) {
30  const res = await fetch(\`https://api.example.com/products/\${id}\`, {
31    next: {
32      revalidate: 3600, // Cache for 1 hour
33      tags: [\`product-\${id}\`, 'products'], // Tags for revalidation
34    },
35  });
36  return res.json();
37}

Hybrid Rendering - Mixing Strategies

In Next.js 15, you can combine different rendering strategies within a single page thanks to Partial Prerendering (PPR):

1// app/store/page.tsx - Hybrid approach
2import { Suspense } from 'react';
3
4// Static part of the page (SSG)
5function StaticHeader() {
6  return (
7    <header>
8      <h1>Quantum Store</h1>
9      <nav>
10        <a href="/products">Products</a>
11        <a href="/categories">Categories</a>
12      </nav>
13    </header>
14  );
15}
16
17// Dynamic part of the page (SSR)
18async function DynamicCart() {
19  const res = await fetch('https://api.example.com/cart', { cache: 'no-store' });
20  const cart = await res.json();
21
22  return (
23    <div>
24      <h2>Your Cart ({cart.items.length} products)</h2>
25      <p>Total: {cart.total} QC</p>
26    </div>
27  );
28}
29
30// ISR page with dynamic elements
31async function ProductList() {
32  const res = await fetch('https://api.example.com/featured', {
33    next: { revalidate: 300 },
34  });
35  const products = await res.json();
36
37  return (
38    <div>
39      {products.map((p: any) => (
40        <div key={p.id}>{p.name} - {p.price} QC</div>
41      ))}
42    </div>
43  );
44}
45
46export default function StorePage() {
47  return (
48    <div>
49      <StaticHeader />
50      <Suspense fallback={<p>Loading cart...</p>}>
51        <DynamicCart />
52      </Suspense>
53      <Suspense fallback={<p>Loading products...</p>}>
54        <ProductList />
55      </Suspense>
56    </div>
57  );
58}

How to Measure Performance in Next.js 15?

Web Vitals Reporting

1// app/layout.tsx
2import { SpeedInsights } from '@vercel/speed-insights/next';
3import { Analytics } from '@vercel/analytics/react';
4
5export default function RootLayout({ children }: { children: React.ReactNode }) {
6  return (
7    <html lang="en">
8      <body>
9        {children}
10        <SpeedInsights />
11        <Analytics />
12      </body>
13    </html>
14  );
15}

Built-in Next.js Profiler

1# Building with profiling
2NEXT_TELEMETRY_DEBUG=1 next build
3
4# Bundle size analysis
5ANALYZE=true next build

When to Use Which Strategy?

| Scenario | Strategy | Why? | |----------|----------|------| | Blog, documentation | SSG | Content doesn't change often | | E-commerce (product list) | ISR (60-300s) | Products change, but not every second | | User dashboard | SSR | Data must always be current | | Home page with promotions | ISR (300-3600s) | Promotions change every few hours | | Admin panel | SSR | Requires current data and authorization | | Landing page | SSG | Maximum speed, static content | | Social feed | SSR + Streaming | Dynamic data, progressive loading |

Summary

The choice of rendering strategy in Next.js 15 should be driven by the specific requirements of the project:

  1. SSG - the fastest option, ideal for static content
  2. ISR - a compromise between speed and data freshness
  3. SSR - full dynamism at the cost of performance
  4. Hybrid (PPR) - the future of rendering, combining all strategies

In the Quantum Metropolis of 2150, engineers base their decisions on data - they measure, analyze, and optimize. Performance benchmarks are a key tool in this process.

Go to CodeWorlds