Recently, while working on a React + Vite project, I ran into a strange runtime error after building my app:
Cannot destructure property 'Request' of undefined as it is undefined
This issue didnāt appear in development mode ā only in the production build.
After some debugging, the fix turned out to be related to how the Istanbul plugin was loaded in the Vite configuration.
Letās go through what happened ![]()
The Original Setup
Hereās the initial vite.config.js:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import istanbul from 'vite-plugin-istanbul';
require("dotenv").config();
export default defineConfig({
plugins: [
react({ include: "**/*.jsx" }),
istanbul({
forceBuildInstrument: true,
include: 'src/*',
exclude: ['node_modules', 'test/'],
extension: ['.js', '.ts', '.dist', '.jsx'],
requireEnv: true,
}),
],
define: {
global: "window",
},
resolve: {
alias: {
"./runtimeConfig": "./runtimeConfig.browser",
},
},
server: { port: 3000 }
})
Everything worked locally, but after running the production build, the app crashed with the error:
Cannot destructure property āRequestā of undefined
The Root Cause
The error happened because the Istanbul plugin, which is meant for test coverage, was included even in the production build.
This plugin is designed to work in a Node.js environment ā it expects Node-only variables like global and process.
When it runs inside the browser build, those variables donāt exist, and the pluginās code ends up breaking parts of the global environment.
Thatās why, when the browser tried to use Request (a built-in web API), it couldnāt find it anymore ā resulting in the error.
In short:
- Istanbul ran in production (by mistake).
- It tried to use Node.js features not available in browsers.
- This broke the global environment and made
Requestundefined.
The Fix: Conditional Plugin Loading + Safe Global Definition
Hereās the updated (working) version of vite.config.js:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
require("dotenv").config();
export default defineConfig(async () => {
const { default: istanbul } = await import('vite-plugin-istanbul');
return {
plugins: [
react({ include: "**/*.jsx" }),
...(process.env.NODE_ENV !== 'production' ? [istanbul({
forceBuildInstrument: true,
include: 'src/*',
exclude: ['node_modules', 'test/'],
extension: ['.js', '.ts', '.dist', '.jsx'],
requireEnv: true,
})] : []),
],
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis', // ā
ensures browser-safe global variable
},
target: 'esnext'
},
},
resolve: {
alias: {
"./runtimeConfig": "./runtimeConfig.browser",
},
},
build: {
outDir: 'dist',
target: 'esnext',
minify: 'esbuild',
inlineDynamicImports: true,
},
server: { port: 3000, open: true }
};
});
Hereās what changed and why it works:
- Dynamic Import for Istanbul Plugin
const { default: istanbul } = await import('vite-plugin-istanbul');
This defers loading the plugin until the environment is initialized.
2. Conditional Plugin Inclusion
...(process.env.NODE_ENV !== 'production' ? [istanbul(...)] : [])
This ensures Istanbul only runs in development and testing, never in production.
3. Safe Global Definition
define: { global: 'globalThis' }
This maps Nodeās global to the browserās globalThis, preventing environment mismatches.
The Outcome
Once the build configuration was updated:
- The production build ran smoothly.
- No more
Requesterrors. - Code coverage reports still worked during testing.
Takeaway
When using Vite plugins that depend on Node.js (like Istanbul for coverage):
- Only load them in non-production environments.
- Avoid static imports that run too early.
- Always map
globalāglobalThisfor browser compatibility.