Jehlani Luciano Logo

Middleware

Guidelines for using Astro Middleware to intercept requests and responses.

astro
          
            ## Astro Middleware Guidelines

1.  Purpose: Middleware allows you to run code before a page or API endpoint is rendered. It can intercept and modify request and response data, enabling dynamic behaviors like authentication, redirects, logging, or HTML rewriting.

2.  Requirement: Middleware functions primarily target on-demand rendered pages (SSR), as they run _at request time_. For pre-rendered static pages, middleware runs _once at build time_. Requires a server adapter to be installed for SSR functionality.

3.  Location: Create a file named `src/middleware.js` or `src/middleware.ts` (or `src/middleware/index.js|ts`).

4.  Structure:

    - Export a named function `onRequest`. This function can be `async`.
    - The function receives two arguments: `context` (an `APIContext`-like object) and `next` (a function).

    ```typescript
    // src/middleware.ts
    import { defineMiddleware } from "astro:middleware";

    export const onRequest = defineMiddleware(async (context, next) => {
      // Code runs before rendering the page/endpoint
      console.log(`Handling request for: ${context.url.pathname}`);

      // Modify context.locals to share data
      context.locals.userRole = "admin"; // Example

      // Proceed to the next middleware or the page render
      const response = await next();

      // Code runs after the page/endpoint has generated a response
      console.log(`Response status: ${response.status}`);

      // Optionally modify the response before sending
      // const html = await response.text();
      // return new Response(html.replace('foo', 'bar'), response);

      return response; // Return the original or modified response
    });
    ```

5.  `context` Object: Provides request information (e.g., `context.request`, `context.url`, `context.cookies`, `context.clientAddress`) and the crucial `context.locals` object.

6.  `context.locals`:

    - An object used to share data between middleware and your pages/endpoints within the same request lifecycle.
    - Populate `context.locals` in middleware (e.g., `context.locals.user = await getUser(...)`).
    - Access shared data in `.astro` files via `Astro.locals` (e.g., `const user = Astro.locals.user;`) or in endpoints via the `context.locals` property.
    - Data persists only for the _current request_.
    - Define the shape of `locals` in `src/env.d.ts` for type safety:
      ```typescript
      // src/env.d.ts
      declare namespace App {
        interface Locals {
          user?: { id: string; email: string };
          // other properties...
        }
      }
      ```

7.  `next()` Function:

    - Calling `await next()` proceeds to the next middleware in the sequence or renders the actual page/endpoint.
    - Crucially, you must `return` the result of `await next()` (or return a new `Response` directly) for the process to continue correctly.
    - `next(newPathOrRequest)`: Can be called with a URL path string, `URL`, or `Request` object to rewrite the request _without_ restarting the middleware chain. Subsequent middleware or the page will receive the rewritten context.

8.  Returning a `Response`: Instead of calling `next()`, middleware can directly return a standard `Response` object (e.g., `return new Response('Unauthorized', { status: 401 })` or `return context.redirect('/login')`). This stops further processing and sends the response immediately.

9.  Chaining Middleware (`sequence`):

    - Import `sequence` from `astro:middleware`.
    - Export `onRequest` as the result of `sequence(middleware1, middleware2, ...)`. Middleware runs in the specified order.

10. Rewriting (`context.rewrite`):

    - Use `return context.rewrite(newPathOrRequest)` to internally forward the request to render a different page/endpoint _without_ changing the browser URL.
    - Important: This _restarts_ the request handling process, meaning _all_ middleware functions (including the one calling `rewrite`) will run again for the rewritten path. Compare this to `next(newPath)`