komal.nakrani
Browse Themes
ThemesCategoriesDocsBlogTips & Tricks
Server Rendering••By Komal Nakrani

Fix: The Middleware Didn't Return a Response in Astro

Astro's 'The middleware didn't return a Response' means onRequest ended without returning next(). Return next(), and await it to edit headers.

#Astro#Errors#Middleware

Astro shows “The middleware didn’t return a Response” when your onRequest function in src/middleware.ts ends without returning anything. The message explains: “Make sure your middleware returns a Response object, either directly or by returning the Response from calling the next function.” The usual bug is calling next() without return, or a code path (often an if branch) that falls through.

Key Takeaways

  • Every path through onRequest must return next() or return a Response.
  • await next() gives you the response to modify; you still have to return it.
  • Redirects: return context.redirect('/login/').
  • With sequence(), each middleware follows the same rule.

The common bug

// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';

export const onRequest = defineMiddleware(async (context, next) => {
  context.locals.startedAt = Date.now();
  next(); // called, but not returned
});

The fix

export const onRequest = defineMiddleware(async (context, next) => {
  context.locals.startedAt = Date.now();
  return next();
});

Branches that fall through

// Wrong: when the user is logged in, nothing is returned
export const onRequest = defineMiddleware(async (context, next) => {
  if (!context.cookies.has('session') && context.url.pathname.startsWith('/account')) {
    return context.redirect('/login/');
  }
  // missing return next()
});

Add return next(); at the end. A linter rule like TypeScript’s noImplicitReturns catches this class of bug.

Changing the response

To add headers, await the response, change it, and return it:

export const onRequest = defineMiddleware(async (_context, next) => {
  const response = await next();
  response.headers.set('X-Frame-Options', 'DENY');
  return response;
});

For a fully different body, return a new Response:

const response = await next();
const html = await response.text();
return new Response(html.replace('{{year}}', String(new Date().getFullYear())), {
  status: response.status,
  headers: response.headers,
});

Returning something that isn’t a Response, such as a string or an object, gives “The middleware returned something that is not a Response object.” Wrap it: return new Response('Not allowed', { status: 403 }).

Middleware on static pages

On prerendered pages, middleware runs once at build time. Logic that reads cookies or headers does nothing useful there; it only has an effect on on-demand routes. The hybrid rendering tip explains which pages render when.

The Astro SSR guide has a full middleware example with sequence() and typed locals. Related: locals must not be reassigned. The middleware docs are the reference.

FAQ

Do I need to await next()?

Only if you want to read or modify the response. return next() is enough otherwise.

Can middleware return early for some paths?

Yes. Check context.url.pathname and return next() immediately for paths you don’t handle.