Skip to main content

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 files
  • hasLoader: Boolean indicating if the component has a loader
  • loaderReturnInfo: Configuration for loader return behavior
    • returnType: Type of loader return ("defer" for async data)
    • hasOptions: Boolean for additional options
    • keys: 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​

  1. Middleware executes before the component loader
  2. Middleware adds data to the dataLoader.componentData object
  3. Loader accesses this data through _dataContext.componentData
  4. Component receives data through useLoaderData and Await

Best Practices​

  1. Error Handling: Always include proper error handling in middlewares
  2. Type Safety: Use TypeScript interfaces for middleware parameters
  3. Initialization: Check if componentData exists before using
  4. Async Operations: Use defer for async data loading
  5. 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:

  1. Middleware Not Executing: Verify path in routes.json is correct
  2. Data Not Available: Check componentData initialization
  3. Type Errors: Ensure proper TypeScript types for data
  4. Loading Issues: Verify defer usage and Suspense boundaries

Back to Index | Previous: Loaders and Data Fetching | Next: Advanced Configurations