Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Missing polyfill for multiple next.js middlewares

License

NotificationsYou must be signed in to change notification settings

z4nr34l/nemo

Repository files navigation

A middleware composition library for Next.js applications that allows you to organize and chain middleware functions based on URL patterns.

codecovQuality Gate Status

Sponsors

Vercel OSS Program

Installation

npm install @rescale/nemo
pnpm add @rescale/nemo
bun add @rescale/nemo

Key Features

  • Path-based middleware routing
  • Global middleware support (before/after)
  • Context sharing between middleware via shared storage
  • Support for Next.js native middleware patterns
  • Request/Response header and cookie forwarding
  • Middleware nesting and composition
  • Built-in logging system accessible in all middleware functions

Middleware Composition

This example shows all possible options of NEMO usage and middlewares compositions, including nested routes:

import{createNEMO}from'@rescale/nemo';exportdefaultcreateNEMO({// Simple route with middleware chain'/api':[// First middleware in the chainasync(request,{ storage})=>{storage.set('timestamp',Date.now());// Continues to the next middleware},// Second middleware accesses shared storageasync(request,{ storage})=>{consttimestamp=storage.get('timestamp');console.log(`Request started at:${timestamp}`);}],// Nested routes using object notation'/dashboard':{// This middleware runs on /dashboardmiddleware:async(request)=>{console.log('Dashboard root');},// Nested route with parameter'/:teamId':{// This middleware runs on /dashboard/:teamIdmiddleware:async(request,{ params})=>{console.log(`Team dashboard:${params.teamId}`);},// Further nesting with additional parameter'/users/:userId':async(request,{ params})=>{console.log(`Team user:${params.teamId}, User:${params.userId}`);}},// Another nested route under /dashboard'/settings':async(request)=>{console.log('Dashboard settings');}},// Pattern matching multiple routes'/(auth|login)':async(request)=>{console.log('Auth page');}});

Each middleware in a chain is executed in sequence until one returns a response or all are completed. Nested routes allow you to organize your middleware hierarchically, matching more specific paths while maintaining a clean structure.

Nested Routes Execution Order

When a request matches a nested route, NEMO executes middleware in this order:

  1. Globalbefore middleware (if defined)
  2. Root path middleware (/) for all non-root requests
  3. Parent middleware (using themiddleware property)
  4. Child middleware
  5. Globalafter middleware (if defined)

If any middleware returns a response (like a redirect), the chain stops and that response is returned immediately.

API Reference

Types

NextMiddleware

typeNextMiddleware=(request:NextRequest,event:NemoEvent)=>NextMiddlewareResult|Promise<NextMiddlewareResult>;

The standard middleware function signature used in NEMO, compatible with Next.js native middleware.

MiddlewareConfig

typeMiddlewareConfig=Record<string,MiddlewareConfigValue>;

A configuration object that maps route patterns to middleware functions or arrays of middleware functions.

GlobalMiddlewareConfig

typeGlobalMiddlewareConfig=Partial<Record<"before"|"after",NextMiddleware|NextMiddleware[]>>;

Configuration for global middleware that runs before or after route-specific middleware.

Main Functions

createNEMO

functioncreateNEMO(middlewares:MiddlewareConfig,globalMiddleware?:GlobalMiddlewareConfig,config?:NemoConfig):NextMiddleware

Creates a composed middleware function with enhanced features:

  • Executes middleware in order (global before → path-matched middleware → global after)
  • Provides shared storage context between middleware functions
  • Handles errors with custom error handlers
  • Supports custom storage adapters

NemoConfig options

interfaceNemoConfig{debug?:boolean;silent?:boolean;errorHandler?:ErrorHandler;enableTiming?:boolean;storage?:StorageAdapter|(()=>StorageAdapter);}

Matchers

To make it easier to understand, you can check the below examples:

Simple route

Matches/dashboard route exactly.

/dashboard

Params

Path parameters allow you to capture parts of the URL path. The general pattern is:paramName whereparamName is the name of the parameter that will be available in the middleware function'sevent.params object.

Named parameters

Named parameters are defined by prefixing a colon to the parameter name (:paramName).

/dashboard/:team

This matches/dashboard/team1 and providesteam param with valueteam1.

You can also place parameters in the middle of a path pattern:

/team/:teamId/dashboard

This matches/team/123/dashboard and providesteamId param with value123.

Multiple parameters

You can include multiple parameters in a single pattern:

/users/:userId/posts/:postId

This matches/users/123/posts/456 and provides parametersuserId: "123", postId: "456".

Custom matching parameters

Parameters can have a custom regexp pattern in parentheses, which overrides the default match:

/icon-:size(\\d+).png

This matches/icon-123.png but not/icon-abc.png and providessize param with value123.

Optional parameters

Parameters can be suffixed with a question mark (?) to make them optional:

/users/:userId?

This matches both/users and/users/123.

Custom prefix and suffix

Parameters can be wrapped in curly braces{} to create custom prefixes or suffixes:

/product{-:version}?

This matches both/product and/product-v1 and providesversion param with valuev1 when present.

Zero or more segments

Parameters can be suffixed with an asterisk (*) to match zero or more segments:

/files/:path*

This matches/files,/files/documents,/files/documents/work, etc.

One or more segments

Parameters can be suffixed with a plus sign (+) to match one or more segments:

/files/:path+

This matches/files/documents,/files/documents/work, etc., but not/files.

OR patterns

You can match multiple pattern alternatives by using parentheses and the pipe character:

/(auth|login)

This matches both/auth and/login.

Unicode support

The matcher fully supports Unicode characters in both patterns and paths:

/café/:item

This matches/café/croissant and providesitem param with valuecroissant.

Parameter Constraints

You can constrain route parameters to match only specific values or exclude certain values:

// Match only if :lang is either 'en' or 'cn'constnemo=newNEMO({"/:lang(en|cn)/settings":[// This middleware only runs for /en/settings or /cn/settings(req)=>{const{ lang}=req.params;// lang will be either 'en' or 'cn'returnNextResponse.next();},],});// Exclude specific values from matchingconstnemo=newNEMO({"/:path(!api)/:subpath":[// This middleware runs for any /:path/:subpath EXCEPT when path is 'api'// e.g., /docs/intro will match, but /api/users will not(req)=>{const{ path, subpath}=req.params;returnNextResponse.next();},],});

Usage Examples

Basic Path-Based Middleware

import{createNEMO}from'@rescale/nemo';exportconstmiddleware=createNEMO({// Simple route'/api':async(request)=>{// Handle API routes},// With parameter'/users/:userId':async(request,event)=>{// Access parameterconsole.log(`User ID:${event.params.userId}`);},// Optional pattern with custom prefix'/product{-:version}?':async(request,event)=>{// event.params.version will be undefined for '/product'// or the version value for '/product-v1'console.log(`Version:${event.params.version||'latest'}`);},// Pattern with custom matching'/files/:filename(.*\\.pdf)':async(request,event)=>{// Only matches PDF filesconsole.log(`Processing PDF:${event.params.filename}`);}});

Using Global Middleware

import{createNEMO}from'@rescale/nemo';exportdefaultcreateNEMO({'/api{/*path}':apiMiddleware,},{before:[loggerMiddleware,authMiddleware],after:cleanupMiddleware,});

Storage API

The Storage API allows you to share data between middleware executions:

import{createNEMO}from'@rescale/nemo';exportdefaultcreateNEMO({'/':[async(req,{ storage})=>{// Set valuesstorage.set('counter',1);storage.set('items',['a','b']);storage.set('user',{id:1,name:'John'});// Check if key existsif(storage.has('counter')){// Get values (with type safety)constcount=storage.get<number>('counter');constitems=storage.get<string[]>('items');constuser=storage.get<{id:number,name:string}>('user');// Delete a keystorage.delete('counter');}}]});

URL Parameters

Access URL parameters through the event's params property:

import{createNEMO}from'@rescale/nemo';exportdefaultcreateNEMO({'/users/:userId':async(request,event)=>{const{ userId}=event.params;console.log(`Processing request for user:${userId}`);}});

Using the Logger

NEMO provides built-in logging capabilities through the event object that maintains consistent formatting and respects the debug configuration:

import{createNEMO}from'@rescale/nemo';exportdefaultcreateNEMO({'/api':async(request,event)=>{// Debug logs (only shown when debug: true in config)event.log('Processing API request',request.nextUrl.pathname);try{// Your API logicconstresult=awaitprocessRequest(request);event.log('Request processed successfully',result);returnNextResponse.json(result);}catch(error){// Error logs (always shown)event.error('Failed to process request',error);// Warning logs (always shown)event.warn('This endpoint will be deprecated soon');returnNextResponse.json({error:'Internal Server Error'},{status:500});}}},undefined,{debug:true});

All logs maintain the "[NEMO]" prefix for consistency with internal framework logs.

Notes

  • Middleware functions are executed in order until a Response is returned
  • The storage is shared between all middleware functions in the chain
  • Headers and cookies are automatically forwarded between middleware functions
  • Supports Next.js native middleware pattern

Motivation

I'm working with Next.js project for a few years now, after Vercel moved multiple/**/_middleware.ts files to a single/middleware.ts file, there was a unfilled gap - but just for now.After a 2023 retro I had found that there is no good solution for that problem, so I took matters into my own hands. I wanted to share that motivation with everyone here, as I think that we all need to remember how it all started.

Hope it will save you some time and would make your project DX better!

About

Missing polyfill for multiple next.js middlewares

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Contributors3

  •  
  •  
  •  

[8]ページ先頭

©2009-2025 Movatter.jp