Remix Utilities
remix-utils is a comprehensive collection of modular utility functions designed to extend the capabilities of applications built with the Remix framework and React Router. As of version 9.3.1, this library offers a wide array of tools covering both server-side and client-side concerns. Its utilities range from advanced data loading patterns like `promiseHash` for concurrent fetches and `timeout` for resilient API calls, to server-side middleware for handling common web challenges such as client IP detection, CORS, CSRF protection, request ID generation, and server timing. Client-side enhancements include functions like `cacheAssets` for efficient browser caching of build artifacts. The package maintains an `active` and consistent release cadence, frequently pushing out minor and patch updates to introduce new features, resolve bugs, and ensure seamless compatibility with the evolving Remix and React Router ecosystems. A key distinguishing factor is its highly modular design, which allows developers to selectively install and import only the specific utilities needed, thereby keeping bundles lean. Many components can even be installed via a shadcn-like registry for simplified management. This approach differentiates it from monolithic utility libraries, promoting a more focused and performant integration within Remix projects.
Common errors
-
Error: Cannot find module 'remix-utils/my-utility' from '/path/to/your/app'
cause Incorrect import path for a specific utility. Many utilities are located under subpath exports, not directly from the root `remix-utils` package.fixCorrect the import statement to use the specific subpath, e.g., `import { promiseHash } from 'remix-utils/promise';` or `import { cacheAssets } from 'remix-utils/cache-assets';`. -
TypeError: (0 , _remix_utils_csrf.createCSRF) is not a function
cause A required optional peer dependency for the specific utility is missing. For example, the CSRF middleware requires `@oslojs/crypto`.fixIdentify the missing dependency by checking the documentation for the specific utility. Install it using `npm add <dependency-name>`, e.g., `npm add @oslojs/crypto`. -
ReferenceError: Zod is not defined
cause Attempting to use `Typed Session Storage` or related Zod-based features after upgrading to `remix-utils` v9.0.0 or later, where these features were removed.fixRefactor your session management. For typed sessions, consider migrating to patterns supported by `@standard-schema/spec` as introduced in v8.8.0, or use standard Remix session storage. -
Invariant failed: Expected a browser environment
cause The `cacheAssets` utility was called in a server-side context (e.g., a Remix loader, action, or `entry.server`).fixMove the `cacheAssets()` call exclusively into your `app/entry.client.ts` or `app/entry.client.tsx` file.
Warnings
- breaking Version 9.0.0 removed `Typed Session Storage` and its dependency on Zod v3. If your application relied on these features for schema-validated sessions, significant refactoring is required.
- breaking Upgrading from v6 to v7 introduced breaking changes that require following a specific upgrade guide. Subsequent major version upgrades (e.g., v8 to v9) also often contain breaking changes due to alignment with Remix and React Router versions.
- gotcha Many utilities in `remix-utils` are exposed as deeply nested or subpath exports (e.g., `remix-utils/promise`, `remix-utils/cache-assets`). Importing directly from `remix-utils` for these specific utilities will lead to module not found errors.
- gotcha Several utilities depend on optional peer dependencies that are not automatically installed with `remix-utils`. Forgetting to install these specific dependencies will lead to runtime errors when using the associated features.
- breaking In v9.0.0, the type for the `middleware` context was marked as read-only. This change impacts middleware functions that attempted to directly mutate the context object.
- gotcha The `cacheAssets` utility is strictly client-side and must only be executed within your `entry.client` file. Attempting to call it in a server-side context (e.g., a loader or action) will result in a runtime error indicating a non-browser environment.
Install
-
npm install remix-utils -
yarn add remix-utils -
pnpm add remix-utils
Imports
- promiseHash
import { promiseHash } from 'remix-utils';import { promiseHash } from 'remix-utils/promise'; - timeout
import { timeout } from 'remix-utils';import { timeout, TimeoutError } from 'remix-utils/promise'; - cacheAssets
import { cacheAssets } from 'remix-utils';import { cacheAssets } from 'remix-utils/cache-assets'; - json (middleware helper)
import { json } from 'remix-utils';import { json } from 'remix-utils/json';
Quickstart
import { json } from "@remix-run/node";
import { promiseHash, timeout, TimeoutError } from "remix-utils/promise";
// Mock API calls
async function getUser(request: Request): Promise<{ id: string; name: string }> {
await new Promise(resolve => setTimeout(resolve, Math.random() * 50));
return { id: "user-123", name: "John Doe" };
}
async function getPosts(request: Request): Promise<{ id: string; title: string }[]> {
await new Promise(resolve => setTimeout(resolve, Math.random() * 150));
return [{ id: "post-a", title: "My First Post" }, { id: "post-b", title: "Another One" }];
}
// A potentially slow external service
async function getExternalData(request: Request): Promise<string> {
await new Promise(resolve => setTimeout(resolve, 200)); // Simulating a slow API
return "External data loaded!";
}
export async function loader({ request }: { request: Request }) {
try {
const { user, posts, externalData } = await promiseHash({
user: getUser(request),
posts: getPosts(request),
// Attach a timeout to a potentially slow external call
externalData: timeout(getExternalData(request), { ms: 100, message: "External data timed out" })
});
return json({ user, posts, externalData });
} catch (error) {
if (error instanceof TimeoutError) {
console.error("Loader timeout error:", error.message);
return json({ error: "One or more external services timed out. Please try again." }, { status: 504 });
}
console.error("Loader error:", error);
return json({ error: "An unexpected error occurred." }, { status: 500 });
}
}