diff --git a/docs/framework/react/start/middleware.md b/docs/framework/react/start/middleware.md index 1952b5f1c1..a86d716f86 100644 --- a/docs/framework/react/start/middleware.md +++ b/docs/framework/react/start/middleware.md @@ -248,11 +248,83 @@ const authMiddleware = createMiddleware().client(async ({ next }) => { }) ``` +## Using Middleware + +Middleware can be used in two different ways: + +- **Global Middleware**: Middleware that should be executed for every request. +- **Server Function Middleware**: Middleware that should be executed for a specific server function. + +## Global Middleware + +Global middleware is registered using the `registerGlobalMiddleware` function. This function receives an array of middleware to be appended to the global middleware array. There is currently no way to remove global middleware once it has been registered. If you need this functionality, please let us know by opening an issue on GitHub. + +Here's an example of registering global middleware: + +```tsx +import { registerGlobalMiddleware } from '@tanstack/start' + +registerGlobalMiddleware({ + middleware: [authMiddleware, loggingMiddleware], +}) +``` + +### Global Middleware Type Safety + +Global middleware types are inherently **detached** from server functions themselves. This means that if a global middleware supplies additional context to server functions or other server function specific middleware, the types will not be automatically passed through to the server function or other server function specific middleware. + +```tsx +// globalMiddleware.ts +registerGlobalMiddleware({ + middleware: [authMiddleware], +}) + +// serverFunction.ts +const authMiddleware = createMiddleware().server(({ next, context }) => { + console.log(context.user) // <-- This will not be typed! + // ... +}) +``` + +To solve this, add the global middleware you are trying to reference to the server function's middleware array. **The global middleware will be deduped to a single entry (the global instance), and your server function will receive the correct types.** + +Here's an example of how this works: + +```tsx +import { authMiddleware } from './authMiddleware' + +const fn = createServerFn().middleware([authMiddleware]).handler(async ({ context }) => { + console.log(context.user) + // ... +}) +``` + ## Middleware Execution Order -Middleware is executed dependency-first, so the following example will execute `a`, `b`, `c` and `d` in that order: +Middleware is executed dependency-first, starting with global middleware, followed by server function middleware. The following example will log the following in this order: + +- `globalMiddleware1` +- `globalMiddleware2` +- `a` +- `b` +- `c` +- `d` ```tsx +const globalMiddleware1 = createMiddleware().server(async ({ next }) => { + console.log('globalMiddleware1') + return next() +}) + +const globalMiddleware2 = createMiddleware().server(async ({ next }) => { + console.log('globalMiddleware2') + return next() +}) + +registerGlobalMiddleware({ + middleware: [globalMiddleware1, globalMiddleware2], +}) + const a = createMiddleware().server(async ({ next }) => { console.log('a') return next() diff --git a/packages/start/src/client/createServerFn.ts b/packages/start/src/client/createServerFn.ts index 713304eb46..6408e783cd 100644 --- a/packages/start/src/client/createServerFn.ts +++ b/packages/start/src/client/createServerFn.ts @@ -1,6 +1,7 @@ import invariant from 'tiny-invariant' import { defaultTransformer } from '@tanstack/react-router' import { mergeHeaders } from './headers' +import { globalMiddleware } from './registerGlobalMiddleware' import type { AnyValidator, Constrain, @@ -292,6 +293,7 @@ function extractFormDataContext(formData: FormData) { function flattenMiddlewares( middlewares: Array, ): Array { + const seen = new Set() const flattened: Array = [] const recurse = (middleware: Array) => { @@ -299,7 +301,11 @@ function flattenMiddlewares( if (m.options.middleware) { recurse(m.options.middleware) } - flattened.push(m) + + if (!seen.has(m)) { + seen.add(m) + flattened.push(m) + } }) } @@ -398,7 +404,10 @@ async function executeMiddleware( env: 'client' | 'server', opts: MiddlewareOptions, ): Promise { - const flattenedMiddlewares = flattenMiddlewares(middlewares) + const flattenedMiddlewares = flattenMiddlewares([ + ...globalMiddleware, + ...middlewares, + ]) const next = async (ctx: MiddlewareOptions): Promise => { // Get the next middleware diff --git a/packages/start/src/client/index.tsx b/packages/start/src/client/index.tsx index b615c39bd0..63db8db204 100644 --- a/packages/start/src/client/index.tsx +++ b/packages/start/src/client/index.tsx @@ -30,6 +30,10 @@ export { type MiddlewareAfterServer, type Middleware, } from './createMiddleware' +export { + registerGlobalMiddleware, + globalMiddleware, +} from './registerGlobalMiddleware' export { serverOnly } from './serverOnly' export { DehydrateRouter } from './DehydrateRouter' export { json } from './json' diff --git a/packages/start/src/client/registerGlobalMiddleware.ts b/packages/start/src/client/registerGlobalMiddleware.ts new file mode 100644 index 0000000000..3d7effc85b --- /dev/null +++ b/packages/start/src/client/registerGlobalMiddleware.ts @@ -0,0 +1,9 @@ +import type { AnyMiddleware } from './createMiddleware' + +export const globalMiddleware: Array = [] + +export function registerGlobalMiddleware(options: { + middleware: Array +}) { + globalMiddleware.push(...options.middleware) +}