remix-utils

repository·main·Indexed 25 days ago

https://github.com/sergiodxa/remix-utils

A collection of utility functions and components designed for React Router. Version 10.0.0 requires React Router v8, React ≥19.2.7, and Node.js ≥22.22.0. Features include CSRF protection, CORS header implementation, client-only and server-only rendering components, asset caching via cacheAssets(), promise handling with promiseHash and timeout(), and utilities for managing search parameters and external scripts.

Tokens
31.9K
Snippets
104
Records
123
Agent score
75%

What's inside remix-utils

  1. Manage locales with useLocales and getClientLocales

    main

    You can manage user locales using a combination of server-side detection and client-side consumption.

    1. Server-side: Use getClientLocales(request) in your root loader to extract locales from the request or headers. Conventionally, return an object with a locales key.
    2. Client-side: Use the useLocales() hook to access these locales. The return type is Locales (string | string[] | undefined), which is compatible with the standard Intl API.
    import { useLocales } from "remix-utils/locales/react";
    import { getClientLocales } from "remix-utils/locales/server";
    
    // in the root loader
    export async function loader({ request }: Route.LoaderArgs) {
      let locales = getClientLocales(request);
      return json({ locales });
    }
    
    // in any route (including root!)
    export default function Component() {
      let locales = useLocales();
      let date = new Date();
      let dateTime = date.toISOString;
      let formattedDate = date.toLocaleDateString(locales, options);
      return <time dateTime={dateTime}>{formattedDate}</time>;
    }
  2. Verify version compatibility for remix-utils

    main

    Before installing, ensure your environment meets the following requirements:

    • React Router: Requires v8.
    • React: Requires ≥19.2.7.
    • Node.js: Requires ≥22.22.0.

    Note: If your application is still using React Router v7, you must stay on Remix Utils 9.x instead of the current version.

  3. Manage optional dependencies in Remix Utils

    main
    To reduce bundle size, Remix Utils now marks all dependencies as optional. You are responsible for installing any dependency required by the utility you choose to use. Each utility documentation mentions its required dependencies.
  4. Implement CSRF protection with AuthenticityTokenProvider and AuthenticityTokenInput

    main

    To protect your forms against CSRF attacks, follow these steps:

    1. Setup the Provider: In your root component, wrap the Outlet with AuthenticityTokenProvider, passing the csrf token obtained from your loader data.
    2. Add Token to Forms: Inside any Form that performs a mutation (POST, PUT, etc.), include the AuthenticityTokenInput component. This adds a hidden input named csrf containing the token.
    3. Custom Field Names: You can change the input name using the name prop, but only if you have also configured createAuthenticityToken with the same name.
    4. Manual Submissions: If using useFetcher or useSubmit, use the useAuthenticityToken hook to retrieve the token and include it manually in your submission data.

    Note: GET forms (like search forms) do not require the authenticity token.

    // 1. Root setup
    import { AuthenticityTokenProvider } from "remix-utils/csrf/react";
    
    let { csrf } = useLoaderData<LoaderData>();
    return (
    	<AuthenticityTokenProvider token={csrf}>
    		<Outlet />
    	</AuthenticityTokenProvider>
    );
    
    // 2. Form usage
    import { Form } from "react-router";
    import { AuthenticityTokenInput } from "remix-utils/csrf/react";
    
    export default function Component() {
    	return (
    		<Form method="post">
    			<AuthenticityTokenInput />
    			<input type="text" name="something" />
    		</Form>
    	);
    }
    
    // 3. Manual fetcher usage
    import { useFetcher } from "react-router";
    import { useAuthenticityToken } from "remix-utils/csrf/react";
    
    export function useMarkAsRead() {
    	let fetcher = useFetcher();
    	let csrf = useAuthenticityToken();
    	return function submit(data) {
    		fetcher.submit({ csrf, ...data }, { action: "/api/mark-as-read", method: "post" });
    	};
    }
  5. Use CORS Middleware

    main

    The CORS middleware simplifies the setup of Cross-Origin Resource Sharing (CORS) headers. It uses the same options as the cors utility from remix-utils/cors.

    Installation:

    bunx shadcn@latest add @remix-utils/middleware-cors

    Usage:

    import { createCorsMiddleware } from "remix-utils/middleware/cors";
    
    export const [corsMiddleware] = createCorsMiddleware({
    	origin: "https://example.com",
    	methods: ["GET", "POST"],
    	allowedHeaders: ["Content-Type", "Authorization"],
    	exposedHeaders: ["X-My-Custom-Header"],
    	maxAge: 3600,
    	credentials: true,
    });
    
    // In your route (e.g., root.tsx):
    export const middleware: Route.MiddlewareFunction[] = [corsMiddleware];
    import { createCorsMiddleware } from "remix-utils/middleware/cors";
    
    export const [corsMiddleware] = createCorsMiddleware({
    	origin: "https://example.com",
    	methods: ["GET", "POST"],
    	allowedHeaders: ["Content-Type", "Authorization"],
    	exposedHeaders: ["X-My-Custom-Header"],
    	maxAge: 3600,
    	credentials: true,
    });
  6. Install optional dependencies for remix-utils

    main

    Some utilities in remix-utils require additional optional dependencies. You should only install these when the specific utility you are using requires them.

    Optional dependencies list:

    • react-router
    • @edgefirst-dev/batcher
    • @edgefirst-dev/jwt
    • @edgefirst-dev/server-timing
    • @oslojs/crypto
    • @oslojs/encoding
    • is-ip
    • intl-parse-accept-language
    • react (should already be in your project)

    To install all optional dependencies at once, run:

    npm add @edgefirst-dev/batcher @edgefirst-dev/jwt @edgefirst-dev/server-timing @oslojs/crypto @oslojs/encoding is-ip intl-parse-accept-language
  7. Upgrade from Remix Utils v6 to v7

    main

    Upgrading from v6 to v7 introduces three major breaking changes:

    1. ESM Only: The package is now published as ESM-only.
    2. Specific Import Paths: Utilities are no longer imported from the root; you must use specific sub-paths (e.g., remix-utils/sse/server).
    3. Optional Dependencies: All dependencies are now optional. You must manually install any dependency required by the specific utility you are using.
  8. Implement Rolling Cookie Middleware

    main

    The rolling cookie middleware prolongs the expiration of a cookie by updating its expiration date on every request.

    Installation:

    bunx shadcn@latest add @remix-utils/middleware-rolling-cookie

    Dependencies: zod, and React Router.

    Usage:

    1. Create the middleware instance with a Cookie or TypedCookie.
    2. Add it to your middleware array in app/root.tsx.

    Note: If you manually set the same cookie in your own loaders or actions, the middleware will detect this and do nothing, preventing conflicts.

    import { createRollingCookieMiddleware } from "remix-utils/middleware/rolling-cookie";
    import { cookie } from "~/cookies";
    
    export const [rollingCookieMiddleware] = createRollingCookieMiddleware({
    	cookie,
    });
    
    // In app/root.tsx
    export const middleware: Route.MiddlewareFunction[] = [rollingCookieMiddleware];
  9. Implement Honeypot to prevent spam bots

    main

    Honeypot is a technique that adds a hidden field to forms. Bots will fill it, but humans won't. To implement it:

    1. Server Setup: Create a honeypot.server.ts to instantiate the Honeypot class.
    2. Root Loader: In your app/root.tsx loader, call honeypot.getInputProps() and return the result to the client.
    3. Root Provider: Wrap your application UI in the HoneypotProvider component using the props returned from the loader.
    4. Form Integration: In any public form, render the HoneypotInputs component. Use a className (e.g., display: none) to hide the field from users.
    5. Action Validation: In your form's action, call honeypot.check(formData). If the honeypot field is filled, it will throw a SpamError.
    // 1. Server Setup (honeypot.server.ts)
    import { Honeypot } from "remix-utils/honeypot/server";
    export const honeypot = new Honeypot({
    	randomizeNameFieldName: false,
    	nameFieldName: "name__confirm",
    	validFromFieldName: "from__confirm",
    	encryptionSeed: undefined,
    });
    
    // 2. Root Loader (app/root.tsx)
    export async function loader() {
    	return json({ honeypotInputProps: honeypot.getInputProps() });
    }
    
    // 3. Root Provider (app/root.tsx)
    import { HoneypotProvider } from "remix-utils/honeypot/react";
    export default function Component() {
    	return (
    		<HoneypotProvider {...honeypotInputProps}>
    			<Outlet />
    		</HoneypotProvider>
    	);
    }
    
    // 4. Form Integration
    import { HoneypotInputs } from "remix-utils/honeypot/react";
    function SomePublicForm() {
    	return (
    		<Form method="post">
    			<HoneypotInputs label="Please leave this field blank" className="your-css-class" />
    		</Form>
    	);
    }
    
    // 5. Action Validation
    import { SpamError } from "remix-utils/honeypot/server";
    export async function action({ request }) {
    	let formData = await request.formData();
    	try {
    		honeypot.check(formData);
    	} catch (error) {
    		if (error instanceof SpamError) {
    			// handle spam
    		}
    	}
    }
  10. Use Secure Headers Middleware

    main

    The secure headers middleware simplifies the setup of security headers for your responses.

    Installation:

    bunx shadcn@latest add @remix-utils/middleware-secure-headers

    Usage:

    import { createSecureHeadersMiddleware } from "remix-utils/middleware/secure-headers";
    
    export const [secureHeadersMiddleware] = createSecureHeadersMiddleware();
    
    // In your route (e.g., root.tsx):
    export const middleware: Route.MiddlewareFunction[] = [secureHeadersMiddleware];

    You can customize the header key-values by passing an options object to createSecureHeadersMiddleware. Options are compatible with Hono's secureHeaders middleware.

    import { createSecureHeadersMiddleware } from "remix-utils/middleware/secure-headers";
    
    export const [secureHeadersMiddleware] = createSecureHeadersMiddleware();