getServerSideProps — Fetching Data on Every Request in Next.js
In this tutorial, you will learn about getserversideprops. We cover key concepts, practical examples, and best practices to help you master this topic.
Next.js getServerSideProps fetches data on each server-side request, enabling dynamic SSR pages with per-request data, authentication checks, personalized content, and custom Caching headers.
What You'll Learn
By the end of this tutorial, you will understand how getServerSideProps works, when to use it, how to access request and response objects, how to handle authentication and redirects, how to set custom cache headers, and best practices for error handling and performance.
Why It Matters
getServerSideProps is the primary data fetching method for dynamic SSR pages in Next.js Pages Router. Understanding it is essential for building pages that need fresh data on every request — user dashboards, search results, real-time data, and authenticated content.
Real-World Use
A real estate listing site uses getServerSideProps for property detail pages. Each request fetches the latest price, availability, and similar properties from the database. The server renders the page with fresh data every time, ensuring users always see current information without client-side loading spinners.
getServerSideProps Request Lifecycle
┌──────────────────────────────────────────────────────────┐
│ getServerSideProps Lifecycle │
├──────────────────────────────────────────────────────────┤
│ │
│ 1. User requests /products/123 │
│ │
│ 2. Next.js matches route → pages/products/[id].js │
│ │
│ 3. Calls getServerSideProps({ params: { id: '123' } }) │
│ │
│ 4. Inside getServerSideProps: │
│ a. Authenticate user from req.cookies │
│ b. Fetch product from database │
│ c. Check authorization │
│ d. Set cache headers │
│ │
│ 5. Returns { props: { product } } │
│ or { notFound: true } │
│ or { redirect: { destination: '/login' } } │
│ │
│ 6. Next.js renders page component with props │
│ Sends HTML + serialized props to client │
│ │
└──────────────────────────────────────────────────────────┘
Think of getServerSideProps like a custom sandwich shop. Each order (request) gets made fresh. The customer walks in, and the sandwich artist (getServerSideProps) asks what they want (params), checks their loyalty card (auth), grabs fresh ingredients (database query), and makes the sandwich (returns props). Every sandwich is made to order — no pre-made sandwiches sitting around.
Core Usage Patterns
// pages/profile.js — Authenticated profile page
export default function Profile({ user }) {
return (
<div>
<h1>Welcome, {user.name}</h1>
<p>Email: {user.email}</p>
<p>Member since: {user.createdAt}</p>
</div>
);
}
export async function getServerSideProps({ req, query }) {
// 1. Authentication — read session cookie
const session = await getSession(req);
if (!session) {
// Redirect to login with return URL
return {
redirect: {
destination: `/login?returnTo=${encodeURIComponent(req.url)}`,
permanent: false
}
};
}
try {
// 2. Fetch user data from database
const user = await db.users.findById(session.userId);
if (!user) {
return { notFound: true };
}
// 3. Return user data as props
return {
props: {
user: {
id: user.id,
name: user.name,
email: user.email,
createdAt: user.createdAt.toISOString()
// Only send what the page needs
}
}
};
} catch (error) {
// 4. Error handling
return {
props: {
error: 'Failed to load profile. Please try again.'
}
};
}
}
Advanced Patterns
// pages/search.js — Search with pagination and caching
export default function Search({ results, query, page, total }) {
return (
<div>
<h1>Search Results for "{query}"</h1>
<p>{total} results found</p>
{results.map(item => (
<div key={item.id}>{item.title}</div>
))}
{page > 1 && <Link href={`/search?q=${query}&page=${page - 1}`}>Previous</Link>}
{page * 10 < total && <Link href={`/search?q=${query}&page=${page + 1}`}>Next</Link>}
</div>
);
}
export async function getServerSideProps({ query }) {
const searchQuery = query.q || '';
const page = parseInt(query.page) || 1;
const limit = 10;
if (!searchQuery || searchQuery.length < 2) {
return {
props: { results: [], query: searchQuery, page: 1, total: 0 }
};
}
const results = await db.search(searchQuery, page, limit);
return {
props: {
results: results.items,
query: searchQuery,
page,
total: results.total
},
// Cache the result for 60 seconds at the CDN level
// After 60s, allow stale content for up to 30s while revalidating
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=30'
}
};
}
// pages/preview.js — Preview mode (bypasses cache)
export async function getServerSideProps({ req, query, preview, previewData }) {
// Preview mode allows editors to see draft content
if (preview) {
const post = await db.posts.findBySlug(query.slug, { includeDrafts: true });
return { props: { post, preview: true } };
}
// Normal request — published content only
const post = await db.posts.findBySlug(query.slug);
return { props: { post, preview: false } };
}
Error Handling and Status Codes
// pages/products/[id].js — Comprehensive error handling
export default function ProductPage({ product, error, notFound }) {
if (error) return <ErrorState message={error} />;
if (!product) return <NotFound />;
return <ProductDetail product={product} />;
}
export async function getServerSideProps({ params, res }) {
try {
const product = await db.products.findById(params.id);
if (!product) {
// Set 404 status code
if (res) res.statusCode = 404;
return { notFound: true };
}
if (product.discontinued) {
// 410 Gone — product no longer available
if (res) res.statusCode = 410;
return {
props: {
error: 'This product has been discontinued.',
product: null
}
};
}
return { props: { product, error: null } };
} catch (error) {
// 500 Internal Server Error
if (res) res.statusCode = 500;
return {
props: {
error: 'Failed to load product. Please try again later.',
product: null
}
};
}
}
Common Mistakes
- Using getServerSideProps for data that does not change per request. If the data is the same for all users, use getStaticProps instead. getServerSideProps runs on every request and increases server load.
- Not returning serializable props. Props must be serializable with JSON.stringify. Do not return Date objects, functions, or circular references.
- Fetching data that the client could fetch. If the data is user-specific and already fetched by the client, consider using client-side fetching instead to reduce server load.
- Not setting cache headers. By default, getServerSideProps responses are not cached. Set Cache-Control headers for public, non-personalized pages.
- Blocking rendering with slow data fetching. If external API calls are slow, the user waits for the full response. Use timeouts and fallbacks for slow dependencies.
Practice Questions
- When should you use getServerSideProps instead of getStaticProps?
- How do you redirect unauthenticated users with getServerSideProps?
- How do you set custom HTTP status codes in getServerSideProps?
- How do you implement caching for getServerSideProps results?
- What data can you access from the context parameter?
Challenge: Build a user dashboard with getServerSideProps: authentication check with redirect to login, user-specific data from a database, search functionality with pagination and cache headers, preview mode for draft content, and proper error handling for 404, 410, and 500 status codes.
FAQ
Mini Project
Build a real-time dashboard with getServerSideProps: authenticated dashboard page that fetches user-specific analytics from a database, search page with query parameters and pagination, product detail page with 404 and 410 handling, cache headers for public product pages, and a preview mode for content editors.
What's Next
You understand getServerSideProps. Now explore Nuxt.js SSR for server-side rendering with Vue.js.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro