Initializing Enclave...

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.req is undefined because getServerSideProps and 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 a handler(req, res) function. Pages that need server-side data use getServerSideProps(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:

  1. Developer writes auth guard: if (!context.req.cookies.session) return res.status(401)
  2. context is the wrong object — it's the GetServerSidePropsContext accidentally passed into an API handler, or vice versa.
  3. context.req is undefined. The if block throws — but the catch block (if any) calls next() or falls through.
  4. 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.

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →