A crawler can request your React site’s HTML without ever running React. If you only look at browser analytics, you can miss those requests entirely.

This tutorial uses WebDecoy to observe requests to a React application served by Express. It starts in monitor mode, so you can inspect detections before choosing whether to block anything. We build WebDecoy; this example uses its Express SDK and cloud dashboard.

Start with where your React site is hosted

React renders your interface. The server or CDN in front of it receives the initial request. That is where this tutorial puts detection.

The example has a simple request path:

Visitor or crawler → Express → WebDecoy → built React site

If your React files live on a static host and your API runs elsewhere, installing this middleware on the API only covers requests to that API. It does not reveal crawlers fetching the separately hosted pages. Use a sensor at the hosting or CDN layer for that traffic.

For Next.js, use the Next.js setup guide, which places detection in its server request hook.

Run the example

Download the React + Express example and extract it. With Node.js 22.12 or later installed, run these commands from the extracted directory:

npm ci
cp .env.example .env.local

Create an API key in your WebDecoy dashboard and add it to .env.local:

WEBDECOY_API_KEY=your_api_key

This is a server credential. Keep it out of React components and do not give it a VITE_ prefix, which is intended for variables exposed to client code.

Then build and start the application:

npm run build
npm start

Open http://127.0.0.1:3110. You are visiting Express serving Vite’s production output. Running the Vite development server separately would bypass this Express middleware.

Put WebDecoy before the page routes

The important part of server.mjs is the middleware order:

import express from 'express';
import { webdecoy } from '@webdecoy/express';
import { fileURLToPath } from 'node:url';
import path from 'node:path';

const app = express();
const dist = fileURLToPath(new URL('./dist/', import.meta.url));

app.use(webdecoy({
  apiKey: process.env.WEBDECOY_API_KEY || undefined,
  mode: 'monitor',
  honeytoken: false,
  skipPaths: ['/assets/'],
}));

app.use('/assets', express.static(path.join(dist, 'assets')));
app.get(['/', '/guide'], (req, res) => {
  res.sendFile(path.join(dist, 'index.html'));
});
app.use((req, res) => res.status(404).send('Not found'));

app.listen(3110, '127.0.0.1');

Both page routes pass through WebDecoy before Express serves the HTML. Built assets bypass analysis. Unknown routes still return 404; the middleware does not turn a request for a nonexistent file into a successful page response.

Monitor mode lets requests continue when WebDecoy would otherwise deny them. honeytoken: false disables automatic HTML trap injection for this walkthrough, keeping the example focused on observing incoming requests.

The local server accepts direct connections. Before deploying behind a proxy, configure Express’s trust proxy setting for your actual hosting topology so client IP attribution is correct. Do not blindly trust forwarding headers supplied by clients.

The downloadable project also logs the request path and decision locally. It avoids logging visitor addresses, full headers, or credentials. Local decisions and cloud reporting are separate: without an API key, those terminal messages do not mean a record reached your dashboard.

Verify reporting with a labeled test request

Send the reserved test user agent to the guide route:

curl -i -A 'WebDecoy-Test/1.0' http://127.0.0.1:3110/guide

The page should return HTTP 200. In WebDecoy, open Detections and look for the Test record. This is an installation check, not a visit from a real AI crawler. The Express integration documentation describes the reserved test request.

If the record is missing, confirm that your key is loaded, restart the server after editing .env.local, and check the server output for errors. Also check the port: a request to a separate Vite server will not run through Express.

The example was tested locally with React 19.3.0, Vite 8.3.1, Express 5.2.1, and @webdecoy/express 0.18.0. Its production build passed. A normal page request and the test request returned 200, an unknown route returned 404, and the built JavaScript asset loaded successfully. Those checks used no API key; verify dashboard delivery with your own key using the steps above.

Read the crawler detections

Once the integration is deployed, inspect the real traffic that arrives. Look at the requested paths, crawler classifications, supporting signals, and request timing.

A name in a user-agent string tells you what the client claims to be. Check any available identity verification before attributing that request to a particular operator. A familiar crawler name alone is not proof.

Also distinguish requesting a page from rendering it. A crawler requesting the HTML of this client-rendered example has not necessarily executed React or read the content React renders. Server request detection cannot establish that by itself.

WebDecoy’s detection list is not a complete access log. Some requests are allowed locally without being reported, and SDK caching can affect reporting frequency. Requests answered by an upstream CDN without reaching Express are outside this setup’s coverage. Use access logs for complete request counts and collection at the CDN for requests served there.

Leave monitor mode enabled while you learn what is visiting. From there, you can decide which crawlers are useful and which behaviors need a response. The AI detection guide explains the categories available for investigating that traffic.

Want to see WebDecoy in action?

Get a personalized demo from our team.

Request Demo