Middleware Configuration in routes.json
Overview
Middlewares in routes.json
allow you to inject data processing logic before your component renders. They can modify the context, prepare data, and interact with loaders.
Configuration
Basic Structure
{
"/path": {
"exact": true,
"key": "unique-key",
"name": "Route Name",
"middlewares": [
"@your-package/path/to/middleware.js"
],
"componentPath": "@your-package/path/to/component.js",
"hasLoader": true,
"hasComponent": true,
"loaderReturnInfo": {
"returnType": "defer",
"hasOptions": false,
"keys": ["dataKey"]
}
}
}
Fields Explanation
middlewares
: Array of paths to middleware fileshasLoader
: Boolean indicating if the component has a loaderloaderReturnInfo
: Configuration for loader return behaviorreturnType
: Type of loader return ("defer" for async data)hasOptions
: Boolean for additional optionskeys
: Array of data keys that the loader will return
Middleware Implementation
Middlewares should export a function with the following signature:
export const middleware = async (
{ context, request, params }, // Route information
dataLoader, // Data loader instance
next // Next middleware function
) => {
// Middleware logic
await next(); // Proceed to next middleware
};
Example Middleware
export const middleware = async ({ context, request, params }, dataLoader, next) => {
const { apolloClient, logger } = context;
try {
// Async data fetching
const query1 = new Promise((resolve) => {
setTimeout(() => {
resolve({ data: 'Some api data' });
}, 1000);
});
logger.info('Data loaded and cached');
// Initialize componentData if needed
dataLoader.componentData = dataLoader.componentData || {};
// Assign async function to componentData
dataLoader.componentData.apiData = async () => await query1;
await next(); // Continue to next middleware
} catch (error) {
logger.error(error, 'Failed to load data:');
throw error;
}
};
Component Implementation
Components can access middleware data through the loader and Remix's useLoaderData
hook:
import React from 'react';
import { Await, useLoaderData } from '@remix-run/react';
import { Spinner } from '@your-ui-library';
import { defer } from '@remix-run/node';
// Loader accessing middleware data
export const loader = async ({ context, _dataContext }) => {
const apiData = _dataContext?.componentData?.apiData();
return defer({
apiData,
});
};
// Component using loader data
export default (props) => {
const loaderData = useLoaderData();
return (
<React.Suspense fallback={<Spinner />}>
<Await resolve={Promise.all([loaderData.apiData])}>
{([{ data }]) => <YourComponent {...props} apiData={data} />}
</Await>
</React.Suspense>
);
};
Data Flow
- Middleware executes before the component loader
- Middleware adds data to the
dataLoader.componentData
object - Loader accesses this data through
_dataContext.componentData
- Component receives data through
useLoaderData
andAwait
Best Practices
- Error Handling: Always include proper error handling in middlewares
- Type Safety: Use TypeScript interfaces for middleware parameters
- Initialization: Check if
componentData
exists before using - Async Operations: Use
defer
for async data loading - Loading States: Provide appropriate loading indicators
Example with Multiple Middlewares
{
"/complex-route": {
"middlewares": [
"@package/auth-middleware.js",
"@package/data-middleware.js",
"@package/logging-middleware.js"
],
"hasLoader": true,
"loaderReturnInfo": {
"returnType": "defer",
"keys": ["authData", "apiData", "logData"]
}
}
}
Middlewares execute in order, each having access to data set by previous middlewares:
// First middleware
export const authMiddleware = async ({ context }, dataLoader, next) => {
dataLoader.componentData = dataLoader.componentData || {};
dataLoader.componentData.authData = async () => ({ user: 'authenticated' });
await next();
};
// Second middleware
export const dataMiddleware = async ({ context }, dataLoader, next) => {
dataLoader.componentData.apiData = async () => ({ data: 'fetched' });
await next();
};
// Component loader
export const loader = async ({ _dataContext }) => {
const [authData, apiData] = await Promise.all([
_dataContext.componentData.authData(),
_dataContext.componentData.apiData()
]);
return defer({
authData,
apiData
});
};
Troubleshooting
Common issues and solutions:
- Middleware Not Executing: Verify path in
routes.json
is correct - Data Not Available: Check
componentData
initialization - Type Errors: Ensure proper TypeScript types for data
- Loading Issues: Verify
defer
usage and Suspense boundaries
Back to Index | Previous: Loaders and Data Fetching | Next: Advanced Configurations