SSR

Bài 6 – Cách hoạt động của Server-Side Rendering trong Next.js

18/11/2025 DaiPhan
Bài 7 / 18

SSR – Server-Side Rendering

SSR (Server-Side Rendering) là cơ chế render trang tại server mỗi khi có request. Đây là phương pháp quan trọng khi dữ liệu thay đổi liên tục hoặc cần SEO tốt nhưng không thể cache lâu. Next.js hỗ trợ SSR ở cả Pages Router và App Router, nhưng App Router mang lại hiệu năng ổn định hơn.


1. SSR là gì?

1.1. Định nghĩa và Use Cases

SSR (Server-Side Rendering) là kỹ thuật render HTML tại server cho mỗi request:

  • Dữ liệu thay đổi thường xuyên (real-time data)
  • Yêu cầu xác thực (authentication required)
  • SEO quan trọng với content động
  • Personalized content theo user

1.2. SSR Flow trong Next.js

User Request → Server → Fetch Data → Render HTML → Send HTML → Client → Hydrate

2. SSR trong App Router

2.1. Cơ bản với fetch()

// app/news/page.tsx – SSR trong App Router
export default async function NewsPage() {
  // Fetch data mới cho mỗi request
  const res = await fetch("https://api.example.com/news", {
    cache: "no-store" // Không cache, luôn fetch mới
  });
  
  const news = await res.json();

  return (
    <div className="news-container">
      <h1 className="text-3xl font-bold mb-6">📰 Tin tức mới nhất</h1>
      <div className="news-grid">
        {news.map((article) => (
          <article key={article.id} className="news-card">
            <h2 className="text-xl font-semibold">{article.title}</h2>
            <p className="text-gray-600 mt-2">{article.summary}</p>
            <time className="text-sm text-gray-500 mt-2 block">
              {new Date(article.publishedAt).toLocaleDateString('vi-VN')}
            </time>
          </article>
        ))}
      </div>
    </div>
  );
}

2.2. SSR với Authentication

// app/dashboard/page.tsx – SSR với user authentication
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'

export default async function DashboardPage() {
  const cookieStore = cookies()
  const token = cookieStore.get('auth-token')
  
  if (!token) {
    redirect('/login')
  }
  
  // Fetch user data với authentication
  const res = await fetch("https://api.example.com/user/profile", {
    cache: "no-store",
    headers: {
      'Authorization': `Bearer ${token.value}`
    }
  });
  
  if (!res.ok) {
    redirect('/login')
  }
  
  const user = await res.json();
  
  return (
    <div className="dashboard">
      <h1>Welcome back, {user.name}!</h1>
      <div className="user-stats">
        <p>Email: {user.email}</p>
        <p>Member since: {new Date(user.createdAt).toLocaleDateString()}</p>
      </div>
    </div>
  );
}

2.3. SSR với Dynamic Parameters

// app/products/[id]/page.tsx – SSR cho product detail
export default async function ProductPage({ 
  params 
}: { 
  params: { id: string } 
}) {
  // Fetch product data theo ID
  const res = await fetch(`https://api.example.com/products/${params.id}`, {
    cache: "no-store"
  });
  
  if (!res.ok) {
    notFound() // Hiển thị 404 page
  }
  
  const product = await res.json();
  
  return (
    <div className="product-detail">
      <div className="product-images">
        <img src={product.image} alt={product.name} />
      </div>
      <div className="product-info">
        <h1>{product.name}</h1>
        <p className="price">${product.price}</p>
        <p className="description">{product.description}</p>
        <button className="add-to-cart">Add to Cart</button>
      </div>
    </div>
  );
}

3. SSR trong Pages Router (Legacy)

3.1. getServerSideProps

// pages/news.tsx (Pages Router)
import { GetServerSideProps } from 'next'

interface NewsArticle {
  id: string
  title: string
  content: string
  publishedAt: string
}

interface NewsPageProps {
  articles: NewsArticle[]
}

export default function NewsPage({ articles }: NewsPageProps) {
  return (
    <div className="news-page">
      <h1>Latest News</h1>
      <div className="articles">
        {articles.map((article) => (
          <article key={article.id}>
            <h2>{article.title}</h2>
            <p>{article.content}</p>
            <time>{article.publishedAt}</time>
          </article>
        ))}
      </div>
    </div>
  );
}

export const getServerSideProps: GetServerSideProps<NewsPageProps> = async (context) => {
  // Access request context
  const { req, res, query, params } = context
  
  // Fetch data
  const response = await fetch('https://api.example.com/news')
  const articles = await response.json()
  
  return {
    props: {
      articles
    }
  }
}

4. So sánh SSR vs SSG vs ISR

4.1. Comparison Table

FeatureSSRSSGISR
Build TimeKhông có
Request TimeKhôngCó (revalidate)
Data FreshnessLuôn mớiStaticConfigurable
PerformanceChậm hơnNhanh nhấtNhanh
SEOTốtTốt nhấtTốt
Use CaseReal-time dataStatic contentPeriodic updates

4.2. Khi nào dùng SSR?

// ✅ Nên dùng SSR:
// - User-specific data
// - Real-time information
// - Authentication required
// - A/B testing

// ❌ Không nên dùng SSR:
// - Static marketing pages
// - Blog posts (dùng SSG/ISR)
// - Product catalogs (dùng ISR)

5. Performance Optimization

5.1. Cache Headers

// app/api/ssr-cache/route.ts
export async function GET(request: Request) {
  const data = await fetchData()
  
  return new Response(JSON.stringify(data), {
    headers: {
      'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=120',
      'CDN-Cache-Control': 'public, max-age=60',
      'Vercel-CDN-Cache-Control': 'public, s-maxage=3600',
    },
  })
}

5.2. Streaming SSR

// app/streaming/page.tsx
import { Suspense } from 'react'

async function SlowComponent() {
  await new Promise(resolve => setTimeout(resolve, 3000))
  return <div>Slow data loaded!</div>
}

export default function StreamingPage() {
  return (
    <div>
      <h1>Streaming SSR Example</h1>
      <Suspense fallback={<div>Loading slow component...</div>}>
        <SlowComponent />
      </Suspense>
    </div>
  )
}

6. Error Handling trong SSR

6.1. Try-catch và Error Boundaries

// app/ssr-page/page.tsx
import { notFound } from 'next/navigation'

export default async function SSRPage() {
  try {
    const res = await fetch('https://api.example.com/data', {
      cache: 'no-store'
    })
    
    if (!res.ok) {
      if (res.status === 404) {
        notFound() // Show 404 page
      }
      throw new Error('Failed to fetch data')
    }
    
    const data = await res.json()
    
    return (
      <div>
        <h1>Data loaded successfully</h1>
        <pre>{JSON.stringify(data, null, 2)}</pre>
      </div>
    )
  } catch (error) {
    console.error('SSR Error:', error)
    
    return (
      <div className="error-page">
        <h1>⚠️ Error loading data</h1>
        <p>Please try again later.</p>
      </div>
    )
  }
}

7. Real-world Examples

7.1. E-commerce với Real-time Inventory

// app/products/[id]/page.tsx
export default async function ProductPage({ params }) {
  // SSR cho real-time inventory và pricing
  const [productRes, inventoryRes, reviewsRes] = await Promise.all([
    fetch(`https://api.example.com/products/${params.id}`),
    fetch(`https://api.example.com/inventory/${params.id}`, { cache: 'no-store' }),
    fetch(`https://api.example.com/reviews/${params.id}`)
  ])
  
  const [product, inventory, reviews] = await Promise.all([
    productRes.json(),
    inventoryRes.json(),
    reviewsRes.json()
  ])
  
  return (
    <div className="product-page">
      <h1>{product.name}</h1>
      <p className="price">${inventory.price}</p>
      <p className="stock">Stock: {inventory.quantity}</p>
      <div className="reviews">
        <h2>Reviews ({reviews.length})</h2>
        {/* Reviews content */}
      </div>
    </div>
  )
}

7.2. Dashboard với Real-time Analytics

// app/dashboard/analytics/page.tsx
export default async function AnalyticsPage() {
  const session = await getServerSession()
  
  if (!session) {
    redirect('/login')
  }
  
  // Fetch real-time analytics cho user
  const analytics = await fetch(`https://api.example.com/analytics/${session.userId}`, {
    cache: 'no-store',
    headers: {
      'Authorization': `Bearer ${session.accessToken}`
    }
  }).then(res => res.json())
  
  return (
    <div className="analytics-dashboard">
      <h1>Analytics Dashboard</h1>
      <div className="metrics">
        <div className="metric">
          <h3>Page Views</h3>
          <p>{analytics.pageViews}</p>
        </div>
        <div className="metric">
          <h3>Unique Visitors</h3>
          <p>{analytics.uniqueVisitors}</p>
        </div>
      </div>
    </div>
  )
}

8. Bài tập thực hành

Bài tập 1: Weather App với SSR

Tạo trang /weather với:

  • Fetch real-time weather data
  • Hiển thị theo location
  • Error handling cho API failures
  • Loading states

Bài tập 2: User Dashboard

Tạo dashboard với:

  • Authentication check
  • Fetch user-specific data
  • Real-time notifications
  • Responsive layout

Bài tập 3: Stock Price Tracker

Tạo stock tracker với:

  • Real-time stock prices
  • Multiple stock symbols
  • Price change indicators
  • Auto-refresh functionality

9. Kết luận bài học

  • SSR: Render mới cho mỗi request, phù hợp cho real-time data
  • Performance: Cần optimize với caching và streaming
  • Use Cases: Authentication, personalization, real-time data
  • Trade-offs: Server cost vs data freshness

🔑 GHI NHỚ QUAN TRỌNG:

  • Dùng SSR cho data thay đổi theo từng request
  • Luôn handle errors trong SSR pages
  • Consider performance impact với high-traffic pages
  • Kết hợp với caching strategies để optimize performance
18 bài học
Bài 7
Tiến độ hoàn thành 39%

Đã hoàn thành 7/18 bài học