How to Fix Next.js getServerSideProps context.req Not Defined in API Routes
Threat/Impact Level: MEDIUM | Exploitability/Downtime Risk: HIGH | Time to Fix: 5 mins
TL;DR
- What broke:
context.reqisundefinedbecausegetServerSidePropsand Next.js API routes are two completely different execution contexts — you cannot use one inside the other. - How to fix it: API routes under
/pages/api/must export ahandler(req, res)function. Pages that need server-side data usegetServerSideProps(context). They are not interchangeable. - Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your broken file and it will detect which pattern you actually need and rewrite it.
The Incident (What Does the Error Mean?)
You will see one of the following at runtime or build time:
TypeError: Cannot read properties of undefined (reading 'req')
at handler (pages/api/user.js:5:23)
// or silently: context.req is undefined, session/cookie reads return null
Immediate consequence: The API endpoint returns a 500, or worse — it silently swallows the error and returns malformed JSON. If you were reading auth cookies or session tokens via context.req.headers.cookie, your auth check is now a no-op. Requests that should be rejected are passing through.
The Attack Vector / Blast Radius
This is not just a crash bug. The dangerous scenario is the silent auth bypass:
- Developer writes auth guard:
if (!context.req.cookies.session) return res.status(401) contextis the wrong object — it's theGetServerSidePropsContextaccidentally passed into an API handler, or vice versa.context.reqisundefined. Theifblock throws — but the catch block (if any) callsnext()or falls through.- The protected route is now fully open.
In a getServerSideProps function mistakenly imported and called inside an API route, the context.resolvedUrl, context.query, and context.locale fields are also undefined, causing downstream rendering logic to produce corrupted page props that get serialized and sent to the client.
Blast radius: Auth bypass → data exfiltration from server-side DB calls that should have been gated. In multi-tenant SaaS apps, this can expose cross-tenant data.
How to Fix It (The Solution)
Basic Fix — Know Which File You're In
The rule is absolute:
| File location | Correct export | Context object |
|---|---|---|
pages/api/*.js |
export default function handler(req, res) |
Node IncomingMessage req, ServerResponse res |
pages/*.js |
export async function getServerSideProps(context) |
GetServerSidePropsContext with .req, .res, .query, .params |
You cannot call getServerSideProps from an API route. Full stop.
Broken API Route Using Wrong Context
- // pages/api/user.js — WRONG
- export default function handler(context) {
- const cookie = context.req.headers.cookie; // context.req is undefined
- const { id } = context.query; // also undefined
- context.res.json({ id }); // crashes here
- }
+ // pages/api/user.js — CORRECT
+ export default function handler(req, res) {
+ const cookie = req.headers.cookie; // direct Node IncomingMessage
+ const { id } = req.query; // parsed query string
+ res.status(200).json({ id });
+ }
Broken Page File Trying to Use API Route Signature
- // pages/dashboard.js — WRONG
- export default async function handler(req, res) {
- const session = req.cookies.session; // works, but this is NOT getServerSideProps
- res.json({ session }); // this is not how pages work
- }
+ // pages/dashboard.js — CORRECT
+ export async function getServerSideProps(context) {
+ const session = context.req.cookies.session ?? null;
+ if (!session) {
+ return { redirect: { destination: '/login', permanent: false } };
+ }
+ return { props: { session } };
+ }
+
+ export default function DashboardPage({ session }) {
+ return <div>Welcome {session}</div>;
+ }
Enterprise Best Practice — Shared Auth Logic Without Context Confusion
The production pattern is to extract auth logic into a framework-agnostic utility that accepts a plain req object, usable from both contexts:
- // Antipattern: duplicating cookie parsing in both getServerSideProps and API routes
- // getServerSideProps: context.req.cookies
- // API route: req.cookies
- // Result: two code paths, one inevitably wrong
+ // lib/auth.js — accepts a plain Node req, works everywhere
+ import { getIronSession } from 'iron-session';
+ import { sessionOptions } from './session-config';
+
+ export async function requireSession(req, res) {
+ const session = await getIronSession(req, res, sessionOptions);
+ if (!session.user) throw new Error('UNAUTHENTICATED');
+ return session.user;
+ }
+
+ // pages/api/user.js
+ import { requireSession } from '../../lib/auth';
+ export default async function handler(req, res) {
+ try {
+ const user = await requireSession(req, res); // plain req ✓
+ res.status(200).json({ user });
+ } catch {
+ res.status(401).json({ error: 'Unauthorized' });
+ }
+ }
+
+ // pages/dashboard.js
+ import { requireSession } from '../lib/auth';
+ export async function getServerSideProps(context) {
+ try {
+ const user = await requireSession(context.req, context.res); // context.req ✓
+ return { props: { user } };
+ } catch {
+ return { redirect: { destination: '/login', permanent: false } };
+ }
+ }
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. ESLint with eslint-plugin-next
This won't catch the context confusion directly, but combined with TypeScript it will:
npm install --save-dev eslint-plugin-next @typescript-eslint/eslint-plugin
2. TypeScript — The Actual Fix (Non-Negotiable)
Strong-type your handlers. TypeScript will scream at you before runtime:
// API route — TypeScript catches wrong signature at compile time
import type { NextApiRequest, NextApiResponse } from 'next';
export default function handler(req: NextApiRequest, res: NextApiResponse) {}
// Page — TypeScript catches wrong context shape at compile time
import type { GetServerSideProps } from 'next';
export const getServerSideProps: GetServerSideProps = async (context) => {};
If you pass a GetServerSidePropsContext where NextApiRequest is expected, the compiler errors immediately. Enable strict: true in tsconfig.json. No exceptions.
3. Pre-commit Hook with tsc --noEmit
# .husky/pre-commit
npx tsc --noEmit
This catches the type mismatch before it ever reaches a PR.
4. Playwright/Vitest API Contract Test
// Smoke test: if the API route crashes, this catches it in CI
test('GET /api/user returns 200 or 401, never 500', async ({ request }) => {
const res = await request.get('/api/user');
expect([200, 401]).toContain(res.status()); // 500 = context confusion bug
});
A 500 from an API route in your test suite means someone merged a context.req bug. Gate your deploys on this.