Remix Middleware
raw JSON →remix-middleware provides an Express-like middleware stack for Remix's `loader` and `action` functions, allowing developers to centralize cross-cutting concerns such as authentication, logging, and response manipulation. Currently at version `0.1.1`, this library is still considered experimental, indicating that its API may evolve rapidly. It differentiates itself by deeply integrating with Remix's server-side data functions and offering specialized middleware for common Remix patterns, including streamlined integration with `remix-auth`. While there isn't a fixed release cadence, development appears active, with several minor releases building out core features and addressing early architectural concerns, particularly around preventing server-only code from being bundled for the browser.
Common errors
error TypeError: (0 , remix_middleware__WEBPACK_IMPORTED_MODULE_X__.createMiddleware) is not a function ↓
tsconfig.json for module and moduleResolution settings (e.g., "module": "esnext", "moduleResolution": "bundler" for newer Node/Remix projects) and your package.json type field (set to "module"). error Error: You did not return a value or a Response from your loader/action. This is a common issue when using external tools or incorrectly wrapping the `mdw.run` call. ↓
mdw.run(props, (ctx) => { /* ... */ }) explicitly assigns ctx.response = yourData or ctx.response = new Response(...). The mdw.response helper can simplify this for simple JSON data. error Property 'user' does not exist on type 'RemixMiddlewareContext<unknown>' ↓
isAuthenticated, explicitly define its context type: export const authed = createMiddleware<AuthCtx<User>>(); where User is your application's user type. Warnings
breaking Version `0.1.0` introduced a significant change to prevent server-side code from being erroneously included in the browser bundle. This involved a new API, meaning direct upgrades from `0.0.x` might require updating how middleware is defined and executed to avoid bundling issues. ↓
gotcha The package is still marked as 'experimental' (first released as `0.0.1` experimental). This implies that the API surface may not be stable and could change in future minor or patch releases, potentially introducing breaking changes without a major version bump. ↓
gotcha For middleware to correctly set the response, you must assign the return value to `ctx.response`. If `ctx.response` is not set by any middleware or the final handler, the Remix `loader` or `action` will not return a valid response, leading to errors. ↓
gotcha When integrating with `remix-auth`, ensure you properly type your `createMiddleware` call with `AuthCtx<UserType>` to gain correct TypeScript inference for `ctx.user`. Forgetting this will result in `ctx.user` being typed as `unknown` or `undefined`. ↓
Install
npm install remix-middleware yarn add remix-middleware pnpm add remix-middleware Imports
- createMiddleware wrong
const { createMiddleware } = require('remix-middleware');correctimport { createMiddleware } from 'remix-middleware'; - isAuthenticated
import { isAuthenticated } from 'remix-middleware'; - AuthCtx wrong
import { AuthCtx } from 'remix-middleware';correctimport type { AuthCtx } from 'remix-middleware';
Quickstart
import { createMiddleware, isAuthenticated } from 'remix-middleware';
import type { ActionFunction, LoaderFunction } from '@remix-run/node'; // Or '@remix-run/react' depending on Remix version
// For remix-auth integration (optional)
// import { Authenticator } from 'remix-auth';
// import { sessionStorage } from './session';
// interface User { id: string; email: string; }
// export const authenticator = new Authenticator<User>(sessionStorage);
// ./app/middleware.ts
export const mdw = createMiddleware();
mdw.use(async (ctx, next) => {
console.log('middleware activated for', ctx.request.url);
// ctx.request and ctx.response are available here
await next();
console.log('middleware completed for', ctx.request.url);
});
// Example using remix-auth middleware (uncomment if using remix-auth)
// export const authed = createMiddleware<AuthCtx<User>>();
// authed.use(isAuthenticated(authenticator, { failureRedirect: '/login' }));
// authed.use(authed.routes());
mdw.use(mdw.routes()); // Important: This must be called at the end of the middleware stack
// ./app/routes/posts/index.tsx
// Assuming you have a file at `~/middleware.ts`
// import { mdw } from '~/middleware';
// import { Form, useLoaderData } from '@remix-run/react';
interface Post { id: string; title: string; }
export const loader: LoaderFunction = (props) =>
mdw.run(props, (ctx) => {
// Simulate fetching data
ctx.response = [
{ id: '1', title: 'My First Post' },
{ id: '2', title: 'A Mixtape I Made Just For You' }
];
});
export const action: ActionFunction = (props) =>
mdw.run(props, async (ctx) => {
const body = await ctx.request.formData();
const post = { id: '3', title: String(body.get('title')) };
ctx.response = post;
});
// export default function Posts() {
// const posts = useLoaderData<Post[]>();
// return (
// <div>
// <h1>Posts</h1>
// <div>{posts.map((post) => (<div key={post.id}>{post.title}</div>))}
// </div>
// <Form method="post"><p><label>Title: <input name="title" type="text" /></label></p><p><button type="submit">Create</button></p></Form>
// </div>
// );
// }