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