How to Access Route Parameter Inside getServerSideProps
Learn why your dynamic route parameter appears as undefined in getServerSideProps and how to correctly extract it from the context object for server-side data fetching.
TL;DR#
When I ran into params is undefined or Cannot read property 'id' of undefined in getServerSideProps, I realized the cause was that I was looking for route parameters in the wrong place—context.query or req.query instead of context.params. I fixed it by destructuring params directly from the context argument and using it to fetch server-side data.
If that doesn't work, scroll to verify the fix — there are two common variants this guide also covers.
What you'll see#
TypeError: Cannot read property 'id' of undefined
at getServerSideProps (pages/posts/[id].js:12:24)
I encountered this error when I tried to access context.query.id or req.query.id inside getServerSideProps, expecting the dynamic route segment (e.g., /posts/123) to appear there. The behavior was the same across development (npm run dev) and production builds, and it reproduced in both local and deployed environments. I found this confusing at first because the naming (query vs params) is counterintuitive.
Root cause#
In Next.js Pages Router, getServerSideProps receives a context object with two distinct properties for routing data: params and query. params contains the dynamic segments from the file-based route (e.g., [id] in pages/posts/[id].js), while query contains the URL query string (e.g., ?page=2 in /posts/123?page=2). I initially mixed these up, which caused the error.
The relevant code path is:
// pages/posts/[id].js
export async function getServerSideProps(context) {
// ❌ Wrong: context.query holds query string, not route params
const { id } = context.query;
// ❌ Also wrong: req.query is undefined in getServerSideProps
// const { id } = context.req.query;
// ✅ Correct: params holds dynamic route segments
const { id } = context.params;
// Fetch data using id...
}
The context object is passed by Next.js at request time and is not the same as the req/res pair used in API routes. context.req exists, but context.req.query is not populated for dynamic routes—only context.query is, and it only includes query string parameters, not route segments.
The fix#
// pages/posts/[id].js
import { createClient } from '@/lib/supabase-client';
export async function getServerSideProps(context) {
const { params } = context;
const { id } = params;
if (!id) {
return {
notFound: true,
};
}
const supabase = createClient();
const { data: post, error } = await supabase
.from('posts')
.select('*')
.eq('id', id)
.single();
if (error || !post) {
return {
notFound: true,
};
}
return {
props: {
post,
},
};
}
That single change resolved the issue for me because context.params is explicitly populated by Next.js with the matched dynamic segments from the file path, and params.id is guaranteed to be a string when the route matches.
Step by step#
- Open
pages/posts/[id].js. - Locate the
getServerSidePropsfunction and find whereidis being accessed. - Replace
context.query.idorcontext.req.query.idwithcontext.params.id. - Add a guard clause (
if (!id) return { notFound: true }) to handle edge cases. - Save and restart the dev server (
npm run dev).
Verify the fix#
Run:
npm run dev
Then visit http://localhost:3000/posts/123 in your browser.
I saw the post with ID 123 rendered, and the terminal logged no TypeError. To confirm params was populated, I temporarily added:
console.log('context.params:', context.params);
I saw:
context.params: { id: '123' }
If you're still seeing the error, two common variants exist:
Variant A — Using query instead of params#
You might be mixing up query string parameters with route parameters. For example, /posts/123?draft=true has:
context.params.id === '123'context.query.draft === 'true'
If you need both, destructure both objects:
export async function getServerSideProps(context) {
const { params, query } = context;
const { id } = params;
const { draft } = query;
// Now id = '123', draft = 'true'
}
Variant B — Catch-all routes ([...slug])#
For catch-all routes like pages/posts/[...slug].js, context.params.slug is an array, not a string:
// URL: /posts/a/b/c
// context.params.slug = ['a', 'b', 'c']
Handle it like this:
export async function getServerSideProps(context) {
const { params } = context;
const slugArray = Array.isArray(params.slug) ? params.slug : [params.slug];
const slug = slugArray.join('/'); // 'a/b/c'
// Use slug to fetch nested data...
}
Why this happens (and how to avoid it next time)#
The root invariant is: route parameters are path-based (params), query parameters are query-string-based (query). Next.js enforces this separation deliberately to avoid ambiguity between URL segments and query strings.
To prevent myself from making this mistake again, I added a TypeScript type guard and ESLint rule. In types/next.d.ts:
import { GetServerSidePropsContext } from 'next';
declare module 'next' {
interface GetServerSidePropsContext<
P = Record<string, any>,
Q = Record<string, any>
> {
params?: P;
query?: Q;
}
}
Then in getServerSideProps, use generics:
export async function getServerSideProps(
context: GetServerSidePropsContext<{ id: string }>
) {
const { id } = context.params; // ✅ Type-safe, no undefined risk
}
Related#
- How to Structure Your Next.js App Router Project for Scale
- Next.js App Router Guide: From Basics to Advanced Patterns
- Deploying Next.js + Supabase to Production
- Next.js & Supabase Masterclass: reliable CI/CD Pipelines with GitHub Actions
- AI Integration for Next.js + Supabase Applications
By mastering the distinction between context.params and context.query, you can avoid common pitfalls in server-side data fetching and build more reliable Next.js applications. Check out the related guides above to deepen your understanding of the framework's capabilities.
Frequently Asked Questions
One email a month — no fluff
RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.
Continue Reading
Disable Turbopack for Next.js Production Build
Disable Turbopack for Next.js 16 production builds to resolve issues with Webpack.
Fix Supabase Auth Session Not Persisting After Refresh
Supabase auth sessions mysteriously disappearing after page refresh? Learn the exact cause and fix it in 5 minutes with this tested solution.
Fix port setting in Next.js
Change the default Next.js port when it collides with another process. Step‑by‑step fix with code, verification, and prevention tips.
Browse by Topic
Find stories that matter to you.
