Arcjet React Router SDK reference
This guide shows how to use the package
@arcjet/react-router.
Its source code is on GitHub.
The code is open source and licensed under Apache 2.0.
What is Arcjet?
Arcjet helps developers protect their apps in just a few lines of code. Bot detection. Rate limiting. Email validation. Attack protection. Data redaction. A developer-first approach to security.Quick start
Section titled “Quick start”See the React Router quick start.
Requirements
Section titled “Requirements”- React Router 7 or later
- Node.js 20 or later, or similar runtime
- ESM
Install
Section titled “Install”npm install @arcjet/react-routerpnpm add @arcjet/react-routeryarn add @arcjet/react-routerConfigure
Section titled “Configure”Build Arcjet clients as few times as possible. That means outside request handlers. If you need different strategies, such as one for logged-in users and one for guests, create two clients and choose which one to use inside the handler.
Options
Section titled “Options”The main way to configure Arcjet is to pass options to the arcjet function.
The required fields are:
key(string) — API key to identify the site in Arcjet (typically throughprocess.env.ARCJET_KEY)rules(Array<ArcjetRule>) — rules to use (order insensitive)
For all available fields,
see ArcjetOptions in the readme.
Get your site key from the Arcjet dashboard.
Set it as an environment variable called ARCJET_KEY in your .env file:
ARCJET_KEY=your_site_key_hereEnvironment variables
Section titled “Environment variables”Next to the convention of using ARCJET_KEY for the Arcjet Cloud API key,
there are several environment variables that affect how Arcjet works.
ARCJET_BASE_URL— an allowed URL to the Cloud API (defaults tohttps://decide.arcjet.com)ARCJET_ENV,MODE,NODE_ENV— whether development mode is onARCJET_LOG_LEVEL— log level to useFLY_APP_NAME,VERCEL,RENDER— hosting platform
Protect
Section titled “Protect”Use the protect function to protect a request from React
Router.
Some rules, such as validateEmail, may need extra properties.
The protect function returns a promise that resolves to a decision.
import arcjetReactRouter, { tokenBucket } from "@arcjet/react-router";import type { Route } from "../routes/+types/home";
const arcjetKey = process.env.ARCJET_KEY;
if (!arcjetKey) { throw new Error("Cannot find `ARCJET_KEY` environment variable");}
const arcjet = arcjetReactRouter({ key: arcjetKey, rules: [ tokenBucket({ capacity: 10, characteristics: ["userId"], interval: 10, mode: "LIVE", refillRate: 5, }), ],});
export async function loader( loaderArguments: Route.LoaderArgs,): Promise<undefined> { // Replace `userId` with your authenticated user ID. const userId = "user123"; const decision = await arcjet.protect(loaderArguments, { requested: 5, userId, });
if (decision.isDenied()) { throw new Response("Forbidden", { statusText: "Forbidden", status: 403 }); }
return undefined;}/** * @import { Route } from "../routes/+types/home" */
import arcjetReactRouter, { tokenBucket } from "@arcjet/react-router";
const arcjetKey = process.env.ARCJET_KEY;
if (!arcjetKey) { throw new Error("Cannot find `ARCJET_KEY` environment variable");}
const arcjet = arcjetReactRouter({ key: arcjetKey, rules: [ tokenBucket({ capacity: 10, characteristics: ["userId"], interval: 10, mode: "LIVE", refillRate: 5, }), ],});
/** * @param {Route.LoaderArgs} loaderArguments * @returns {Promise<undefined>} */export async function loader(loaderArguments) { // Replace `userId` with your authenticated user ID. const userId = "user123"; const decision = await arcjet.protect(loaderArguments, { requested: 5, userId, });
if (decision.isDenied()) { throw new Response("Forbidden", { statusText: "Forbidden", status: 403 }); }
return undefined;}Decision
Section titled “Decision”The ArcjetDecision that protect resolves to has the following fields:
conclusion("ALLOW","DENY", or"ERROR") — what to do with the requestid(string) — ID for the request; local decisions start withlreq_and remote ones withreq_ip(ArcjetIpDetails) — analysis of the client IP addressreason(ArcjetReason) — more info about the conclusionresults(Array<ArcjetRuleResult>) — results of each rulettl(number) — time-to-live for the decision in seconds;"DENY"decisions are cached by@arcjet/react-routerfor this duration
This top-level decision takes the results from each "LIVE" rule into account.
If one of them is "DENY" then the overall conclusion will be "DENY".
Otherwise, if one of them is "ERROR", then "ERROR".
Otherwise, it will be "ALLOW".
The reason and ttl fields reflect this conclusion.
To illustrate,
when a bot rule returns an error and a validate email rule returns a deny,
the overall conclusion is "DENY",
while the "ERROR" is available in the results.
The results of "DRY_RUN" rules do not affect this overall decision,
but are included in results.
The ip field is available when the Cloud API was called and contains
IP geolocation and reputation info.
You can use this field to customize responses or you can use
filter rules to make decisions based on it.
See the
IP geolocation and
IP reputation
blueprints for more info.
Errors
Section titled “Errors”Arcjet fails open so that a service issue, misconfiguration, or
network timeout does not block requests.
Such errors should in many cases be logged but otherwise treated as "ALLOW"
decisions.
The reason.message field has more info on what occured.
import arcjetReactrouter, { filter } from "@arcjet/react-router";import type { Route } from "../routes/+types/home";
const arcjetKey = process.env.ARCJET_KEY;
if (!arcjetKey) { throw new Error("Cannot find `ARCJET_KEY` environment variable");}
const arcjet = arcjetReactrouter({ key: arcjetKey, rules: [ // This broken expression will result in an error decision: filter({ deny: ['ip.src.country is "'] }), ],});
export async function loader( loaderArguments: Route.LoaderArgs,): Promise<undefined> { const decision = await arcjet.protect(loaderArguments);
if (decision.isErrored()) { console.warn("Arcjet error", decision.reason.message); }
if (decision.isDenied()) { throw new Response("Forbidden", { statusText: "Forbidden", status: 403 }); }
return undefined;}/** * @import { Route } from "../routes/+types/home" */
import arcjetReactrouter, { filter } from "@arcjet/react-router";
const arcjetKey = process.env.ARCJET_KEY;
if (!arcjetKey) { throw new Error("Cannot find `ARCJET_KEY` environment variable");}
const arcjet = arcjetReactrouter({ key: arcjetKey, rules: [ // This broken expression will result in an error decision: filter({ deny: ['ip.src.country is "'] }), ],});
/** * @param {Route.LoaderArgs} loaderArguments * @returns {Promise<undefined>} */export async function loader(loaderArguments) { const decision = await arcjet.protect(loaderArguments);
if (decision.isErrored()) { console.warn("Arcjet error", decision.reason.message); }
if (decision.isDenied()) { throw new Response("Forbidden", { statusText: "Forbidden", status: 403 }); }
return undefined;}Custom logs
Section titled “Custom logs”You can use a custom log interface matching pino
to change the default behavior.
Using pino-pretty as an example:
npm install pino pino-prettypnpm add pino pino-prettyyarn add pino pino-prettyThen, create a custom logger that will log to JSON in production and pretty print in development:
import arcjetReactRouter from "@arcjet/react-router";import pino from "pino";
const arcjetKey = process.env.ARCJET_KEY;
if (!arcjetKey) { throw new Error("Cannot find `ARCJET_KEY` environment variable");}
const arcjet = arcjetReactRouter({ key: arcjetKey, log: pino({ // Warn in development, debug otherwise. level: process.env.ARCJET_LOG_LEVEL || (process.env.ARCJET_ENV === "development" ? "debug" : "warn"), // Pretty print in development, JSON otherwise. transport: process.env.ARCJET_ENV === "development" ? { options: { colorize: true }, target: "pino-pretty" } : undefined, }), rules: [ // … ],});Custom client
Section titled “Custom client”You can pass a client to change the behavior when connecting to the Cloud API.
Use createRemoteClient to create a client.
import arcjetReactRouter, { createRemoteClient } from "@arcjet/react-router";
const arcjetKey = process.env.ARCJET_KEY;
if (!arcjetKey) { throw new Error("Cannot find `ARCJET_KEY` environment variable");}
const arcjet = arcjetReactRouter({ key: arcjetKey, client: createRemoteClient({ timeout: 3000 }), rules: [ // … ],});