Loading...
Loading...
SSR (Server-Side Rendering) is a strategy where HTML is generated on the server on every user request. Unlike SSG, the page is not created ahead of time but is built in real time.
The browser sends a request to the server. For example, a user opens their profile page on Hack Frontend.
Next.js runs the server component: accesses the database, checks authorization, collects data and generates HTML.
The user receives ready HTML with up-to-date data. Then React hydrates the page on the client, making it interactive.
In the App Router SSR is triggered when a component uses dynamic data. Just add cache: 'no-store' to a fetch call or use dynamic functions (cookies(), headers()):
// app/profile/page.tsx
import { cookies } from 'next/headers'
import { db } from '@/lib/db'
export default async function ProfilePage() {
const session = cookies().get('session')
if (!session) return <LoginPrompt />
const user = await db.user.findUnique({
where: { sessionId: session.value }
})
return (
<div>
<h1>Hello, {user.name}!</h1>
<p>Problems solved: {user.solvedProblems.length}</p>
</div>
)
}
Using cookies() automatically makes this page dynamic (SSR). Next.js understands that the result depends on the request and cannot be cached.
You can explicitly disable caching for fetch requests:
// app/feed/page.tsx
export default async function FeedPage() {
const res = await fetch('https://api.hackfrontend.com/feed', {
cache: 'no-store'
})
const posts = await res.json()
return <FeedList posts={posts} />
}
Or set the mode for an entire route segment:
// app/dashboard/layout.tsx
export const dynamic = 'force-dynamic'
export default function DashboardLayout({
children
}: {
children: React.ReactNode
}) {
return <div className="dashboard">{children}</div>
}
In the Pages Router SSR uses getServerSideProps:
// pages/profile.tsx
export async function getServerSideProps(context) {
const { req } = context
const session = req.cookies.session
if (!session) {
return { redirect: { destination: '/auth/login', permanent: false } }
}
const user = await db.user.findUnique({
where: { sessionId: session }
})
return { props: { user } }
}
export default function ProfilePage({ user }) {
return (
<div>
<h1>Hello, {user.name}!</h1>
<p>Problems solved: {user.solvedProblems.length}</p>
</div>
)
}
Good for:
Not suitable for:
| SSG | SSR | |
|---|---|---|
| When generated | At build time | On every request |
| Response speed | Instant (CDN) | Depends on server |
| Data freshness | At build time | Always current |
| Server load | None | On every request |
| Personalization | No | Yes |
Important:
SSR increases Time to First Byte (TTFB) because the server spends time rendering before sending the response. If the page makes heavy database queries, the user will wait. Use SSR deliberately, not by default.
In the App Router Next.js supports streaming HTML. This lets you send parts of the page as they become ready without waiting for all requests to finish:
// app/dashboard/page.tsx
import { Suspense } from 'react'
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<p>Loading stats...</p>}>
<UserStats />
</Suspense>
<Suspense fallback={<p>Loading activity...</p>}>
<RecentActivity />
</Suspense>
</div>
)
}
The user sees the heading and fallbacks immediately while data loads progressively. This significantly improves perceived speed.