ImportRouter routes to dynamic import functions. It supports lazy loading, but it is also useful when build or deployment tooling needs explicit route import boundaries.
It solves both on-demand loading and tooling visibility for route modules in larger applications.
import ImportRouter from '@stackpress/ingest/plugin/ImportRouter';
const router = new ImportRouter(actionRouter, listen);
// Route to dynamic imports
router.get('/users/:id', () => import('./routes/user.js'));
router.post('/users', () => import('./routes/create-user.js'));
// Conditional imports
router.get('/admin/*', () => {
if (isProduction) {
return import('./routes/admin-prod.js');
}
return import('./routes/admin-dev.js');
});- Properties
- HTTP Method Routing
- Event-Based Routing
- Creating Actions from Imports
- Using Other ImportRouters
- Dynamic Import Functions
- Code Splitting Benefits
- Error Handling
- Integration with ActionRouter
- Best Practices
The following properties are available when instantiating an ImportRouter.
| Property | Type | Description |
|---|---|---|
imports |
Map<string, Set<ImportRouterTaskItem<R, S, X>>> |
Map of event names to import function configurations (readonly) |
The following examples show how to define import-based routes for different HTTP methods.
// GET routes with dynamic imports
router.get('/users', () => import('./routes/users/list.js'));
router.get('/users/:id', () => import('./routes/users/get.js'));
// POST routes
router.post('/users', () => import('./routes/users/create.js'));
// PUT routes
router.put('/users/:id', () => import('./routes/users/update.js'));
// DELETE routes
router.delete('/users/:id', () => import('./routes/users/delete.js'));
// PATCH routes
router.patch('/users/:id', () => import('./routes/users/patch.js'));
// OPTIONS routes
router.options('/users', () => import('./routes/users/options.js'));
// HEAD routes
router.head('/users', () => import('./routes/users/head.js'));
// CONNECT and TRACE are also available
router.connect('/proxy', () => import('./routes/proxy/connect.js'));
router.trace('/users', () => import('./routes/users/trace.js'));
// Handle any method
router.all('/health', () => import('./routes/health.js'));Parameters
| Parameter | Type | Description |
|---|---|---|
path |
string |
Route path with optional parameters (:id) |
action |
ImportRouterAction<R, S, X> |
Function that returns a dynamic import |
priority |
number |
Priority level (default: 0). Can be negative. Higher numbers run first, then ties follow definition order. |
Returns
The ImportRouter instance to allow method chaining.
The following example shows how to route events to dynamic imports.
// Route custom events to imports
router.on('user-login', () => import('./handlers/user-login.js'));
router.on('data-updated', () => import('./handlers/data-updated.js'));
// Route with regex patterns
router.on(/^email-.+$/, () => import('./handlers/email-handler.js'));Parameters
| Parameter | Type | Description |
|---|---|---|
event |
string|RegExp |
Event name or pattern |
entry |
ImportRouterAction<R, S, X> |
Function that returns a dynamic import |
priority |
number |
Priority level (default: 0). Can be negative. Higher numbers run first, then ties follow definition order. |
Returns
The ImportRouter instance to allow method chaining.
The following example shows how import functions are converted to executable actions.
// Internal method - creates action from import function
const action = router.action(
'GET /users',
() => import('./routes/users.js'),
0
);
// The action executes the import and calls the default export
await action(request, response, context);Parameters
| Parameter | Type | Description |
|---|---|---|
event |
string |
Event name for tracking |
action |
ImportRouterAction<R, S, X> |
Function that returns a dynamic import |
priority |
number |
Priority level (default: 0). Can be negative. Higher numbers run first, then ties follow definition order. |
Returns
An async function that executes the import and calls the default export.
The following example shows how to merge imports from another router.
const apiRouter = new ImportRouter(actionRouter, listen);
apiRouter.get('/api/users', () => import('./api/users.js'));
const mainRouter = new ImportRouter(actionRouter, listen);
mainRouter.use(apiRouter); // Merges import configurationsParameters
| Parameter | Type | Description |
|---|---|---|
router |
ImportRouter<R, S, X> |
Another ImportRouter to merge imports from |
Returns
The ImportRouter instance to allow method chaining.
Import functions provide flexible module loading with conditional logic.
The following example shows a basic dynamic import function.
router.get('/users', () => import('./routes/users.js'));
// The imported module should export a default function
// ./routes/users.js
export default async function UsersIndex({ res }) {
const users = await getUsers();
res.setResults(users);
}The following example shows how to implement conditional imports based on runtime conditions.
router.get('/dashboard', () => {
const userRole = getCurrentUserRole();
if (userRole === 'admin') {
return import('./routes/admin-dashboard.js');
} else if (userRole === 'user') {
return import('./routes/user-dashboard.js');
} else {
return import('./routes/guest-dashboard.js');
}
});The following example shows how to use different imports based on environment.
router.get('/debug', () => {
if (process.env.NODE_ENV === 'development') {
return import('./routes/debug/full.js');
} else {
return import('./routes/debug/minimal.js');
}
});The following example shows how to use feature flags to control imports.
router.get('/new-feature', () => {
if (isFeatureEnabled('new-ui')) {
return import('./routes/new-feature-v2.js');
} else {
return import('./routes/new-feature-v1.js');
}
});The following example shows how to perform async operations before importing.
router.get('/dynamic', async () => {
// Can perform async operations before importing
const config = await loadConfiguration();
if (config.useNewHandler) {
return import('./routes/new-handler.js');
} else {
return import('./routes/old-handler.js');
}
});ImportRouter enables lazy loading while also exposing import boundaries through router.imports.
The following examples show how ImportRouter makes route imports visible to builders and deployment scripts.
// Large admin panel is only loaded when needed
router.get('/admin/*', () => import('./routes/admin/index.js'));
// Heavy data processing is split into separate chunks
router.post('/process-data', () => import('./routes/data-processor.js'));
// Third-party integrations are loaded on demand
router.get('/integrations/:service', () => {
const service = getServiceFromPath();
return import(`./integrations/${service}.js`);
});ImportRouter is useful when you want:
- lazy loading
- route-aware build scripts
- deployment packaging based on route modules
- explicit import metadata through
router.imports - On-Demand Loading: Route handlers are loaded only when accessed
- Better Caching: Individual route chunks can be cached separately
- Progressive Loading: Users only download code for features they use
The following example shows how bundlers can analyze import patterns for optimization.
// Bundlers can analyze import patterns for optimization
console.log(router.imports);
// Map {
// 'GET /users' => Set([{
// import: () => import('./routes/users.js'),
// priority: 0
// }]),
// 'POST /users' => Set([{
// import: () => import('./routes/create.js'),
// priority: 0
// }])
// }ImportRouter provides robust error handling for dynamic imports.
The following example shows how to handle import failures with fallback strategies.
router.get('/fallback-example', async () => {
try {
return await import('./routes/primary.js');
} catch (error) {
console.warn('Primary route failed, using fallback');
return await import('./routes/fallback.js');
}
});The following example shows how to validate imported modules.
router.get('/validated', async () => {
const module = await import('./routes/example.js');
if (!module.default || typeof module.default !== 'function') {
throw new Error('Invalid route module: missing default export');
}
return module;
});The following example shows how to implement graceful degradation when imports fail.
router.get('/optional-feature', () => {
return import('./routes/optional.js').catch(() => {
// Return a minimal handler if the feature module fails
return {
default: async (req, res, ctx) => {
res.setError('Feature not available', {}, [], 503);
return false;
}
};
});
});ImportRouter works as an extension of ActionRouter, sharing the same event system and routing capabilities.
The following example shows how ImportRouter integrates with ActionRouter.
import ActionRouter from '@stackpress/ingest/plugin/ActionRouter';
const actionRouter = new ActionRouter(context);
// ImportRouter is automatically available
actionRouter.import.get('/users', () => import('./routes/users.js'));
// Or create standalone
const importRouter = new ImportRouter(actionRouter, listen);The following example shows how to combine different routing approaches.
// Combine different routing approaches
actionRouter.get('/immediate', immediateHandler);
actionRouter.entry.get('/file-based', './routes/file.js');
actionRouter.import.get('/lazy', () => import('./routes/lazy.js'));
actionRouter.view.get('/template', './views/template.hbs');
// All routes work together in the same systemThe following guidelines help ensure effective use of ImportRouter in production applications.
The following examples show route boundaries that are useful for lazy loading, packaging, and deployment tooling.
// Split by feature boundaries
router.get('/auth/*', () => import('./features/auth/routes.js'));
router.get('/billing/*', () => import('./features/billing/routes.js'));
router.get('/analytics/*', () => import('./features/analytics/routes.js'));
// Split heavy dependencies
router.get('/pdf-export', () => import('./routes/pdf-export.js')); // Heavy PDF library
router.get('/image-process', () => import('./routes/image-process.js')); // Heavy image libraryThe following example shows how to preload critical routes for better performance.
// Preload critical routes
const criticalRoutes = [
() => import('./routes/home.js'),
() => import('./routes/login.js'),
() => import('./routes/dashboard.js')
];
// Preload during idle time
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
criticalRoutes.forEach(importFn => importFn());
});
}The following example shows how to implement retry logic for unstable imports.
// Implement retry logic
router.get('/retry-example', async () => {
let attempts = 0;
const maxAttempts = 3;
while (attempts < maxAttempts) {
try {
return await import('./routes/unstable.js');
} catch (error) {
attempts++;
if (attempts >= maxAttempts) {
throw error;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
}
}
});The following example shows different strategies for different environments.
// Different strategies for different environments
const isDev = process.env.NODE_ENV === 'development';
router.get('/dev-tools', () => {
if (isDev) {
return import('./routes/dev-tools.js');
} else {
return Promise.resolve({
default: (req, res, ctx) => {
res.setError('Not available in production', {}, [], 404);
return false;
}
});
}
});The following example shows how to implement type safety with ImportRouter.
// Type-safe import functions
interface RouteModule {
default: (req: Request, res: Response, ctx: Context) => Promise<boolean>;
}
router.get('/typed', (): Promise<RouteModule> => {
return import('./routes/typed.js');
});The following example shows how to track import patterns for optimization.
// Track import patterns for optimization
const importStats = new Map();
router.get('/tracked', () => {
const route = './routes/tracked.js';
importStats.set(route, (importStats.get(route) || 0) + 1);
return import(route);
});
// Log popular routes for optimization
setInterval(() => {
console.log('Import statistics:', importStats);
}, 60000);