Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions website/pages/docs/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const meta = {
},
'development-mode': '',
'going-to-production': '',
'production-build-optimization': '',
'scaling-graphql': '',
};

Expand Down
340 changes: 340 additions & 0 deletions website/pages/docs/production-build-optimization.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,340 @@
---
title: Optimize your GraphQL build for production
description: Bundle, tree-shake, and configure GraphQL.js applications for production, and skip validation safely for trusted schemas and operations.
---

import { Callout } from 'nextra/components';

When you deploy your GraphQL application to production, you
want to ship the smallest, fastest bundle you can. This guide shows you
the general build techniques that apply to any JavaScript application, plus
the few things that are specific to GraphQL.js.

## What production builds change for GraphQL.js

Optimizing a GraphQL.js application for production is mostly the same work as
optimizing any JavaScript application: you bundle your code, eliminate what you
don't use, and minify the result. On top of that general work, there are two
GraphQL.js-specific things to understand—one that your environment controls, and
a few validation steps you can choose to skip for trusted schemas and operations.

### The one behavior tied to NODE_ENV

Internally, GraphQL.js reads `process.env.NODE_ENV` in a single place: its
`instanceOf` check, which backs every type predicate such as `isObjectType`
and `isSchema`. In development, that check does extra work to detect when two
copies of the `graphql` package are loaded at once—a common and hard-to-debug
problem—and throws a descriptive error. In production, it falls back to a plain
`instanceof`.

Setting `NODE_ENV=production` removes this small per-call overhead and lets your
bundler drop the development-only code path. It does **not** disable schema
validation, change how errors are formatted, or turn off introspection—those are
separate, explicit choices. For a full explanation of development mode, the
duplicate-module check, and how this changes in v17, see
[Development Mode](./development-mode).

<Callout type="info">
In GraphQL.js v17 this behavior is no longer driven by `NODE_ENV`. It is
selected by the `production` and `development` conditional exports instead, with
production behavior enabled by default. See
[graphql-js#4551](https://github.com/graphql/graphql-js/pull/4551).
</Callout>

### Validation you can skip for trusted schemas and operations

By default, `validate()` and `execute()` call `assertValidSchema()`, and servers
typically call `validate()` on every incoming operation. These checks are
valuable while you develop, but redundant once a schema and its operations are
known to be correct. GraphQL.js lets you opt out:

- **Trust a schema.** Build it with `assumeValid` so the automatic schema
validation short-circuits:

```js
const schema = new GraphQLSchema({ query, assumeValid: true });
```

When building from SDL, pass `assumeValid` or `assumeValidSDL` to
`buildSchema`, `buildASTSchema`, or `extendSchema` to skip SDL validation.
- **Trust an operation.** For persisted or otherwise pre-validated operations,
you can skip the per-request `validate()` call and execute the parsed document
directly.

Only skip validation for schemas and operations you control. Validating
untrusted, client-supplied operations is a security boundary, not just a
development convenience.

## Build tools and bundling

Build tools are programs that transform your source code into files ready
for production deployment. Build tools perform several transformations
on your code:

- **Combine files**: Take your source code spread across many files and
combine them into one or more bundles.
- **Transform code**: Convert modern JavaScript syntax to versions that work
in your target environments.
- **Remove unused code**: Analyze your code and eliminate functions, variables,
and entire modules that your application never uses.
- **Replace variables**: Substitute configuration values
(like environment variables) with their actual values.
- **Compress files**: Minimize file sizes by removing whitespace, shortening
variable names, and applying other size reductions.

Some common build tools include [Webpack](https://webpack.js.org/),
[Vite](https://vite.dev/), [Rollup](https://rollupjs.org/),
[esbuild](https://esbuild.github.io/), and [Parcel](https://parceljs.org/).
Each tool has different configuration syntax, but supports the same
core concepts needed for GraphQL production preparation.

## Configure environment variables

Setting `NODE_ENV` to `'production'` at build time is what lets your bundler
drop the development-only `instanceOf` path described above. Many other
libraries (including Express) follow the same convention, so a single setting
optimizes your whole dependency tree.

### Set NODE_ENV to production

Ensure your build tool replaces `process.env.NODE_ENV` with the string
`'production'` during the build process, not at runtime.

```js
// Before build-time replacement
if (process.env.NODE_ENV !== 'production') {
// GraphQL.js runs its development-only instanceof safety check here
}
```

Once your build tool substitutes `'production'` for `process.env.NODE_ENV`, the
condition becomes statically false, and dead code elimination removes the branch
entirely from your production bundle.

## Enable dead code elimination

Dead code elimination (also called tree shaking) is essential for removing
GraphQL.js development code from your production bundle.

### Configure your build tool

Most build tools require specific configuration to enable aggressive
dead code elimination:

- Mark your project as side-effect free. This tells your build tool that it's
safe to remove any code that isn't explicitly used.
- Use ES modules. Modern syntax (import/export) enables better code analysis
than older CommonJS syntax (require/exports).
- Enable unused export removal. Configure your build tool to remove functions
and variables that are exported but never imported.
- Configure minification. Set up your minifier to remove unreachable code after
environment variable replacement.

### Configuration pattern

While syntax varies by tool, most build tools support this pattern:

```js
{
"optimization": {
"usedExports": true,
"sideEffects": false
}
}
```

This configuration enables the build tool to safely remove any GraphQL.js code that
won't execute in production.

## Handle browser compatibility

If your GraphQL application runs in web browsers, you need to address Node.js
compatibility issues.

### Provide process polyfills

GraphQL.js assumes Node.js globals like `process` are available. Browsers don't
have these globals, so your build tool needs to provide them or replace references
to them.

Most build tools let you define global variables that get replaced throughout
your code:

```js
{
"define": {
"globalThis.process": "true"
}
}
```

This replaces any reference to `process` with a minimal object that satisfies
GraphQL.js's needs.

### Avoid Node.js-specific APIs

Ensure your GraphQL client code doesn't use Node.js-specific APIs like `fs`
(file system) or path. These APIs don't exist in browsers and will cause runtime
errors.

## Configure code splitting

Code splitting separates your GraphQL code into its own bundle file, which can
improve loading performance and caching. Benefits of code splitting include
better caching, parallel loading, and selective loading.

### Basic code splitting configuration

Most build tools support splitting specific packages into separate bundles:

```js
{
"splitChunks": {
"cacheGroups": {
"graphql": {
"test": "/graphql/",
"name": "graphql",
"chunks": "all"
}
}
}
}
```

This configuration creates a separate bundle file containing all
GraphQL-related code.

## Apply build tool configurations

These examples show how to apply the universal principles using
different build tools. Adapt these patterns to your specific tooling.

Note: These are illustrative examples showing common patterns. Consult
your specific build tool's documentation for exact syntax and available features.

### Webpack configuration

Webpack uses plugins and configuration objects to control the build process:

```js
import webpack from 'webpack';

export default {
mode: 'production',

plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
'globalThis.process': JSON.stringify(true),
}),
],

optimization: {
usedExports: true,
sideEffects: false,
},
};
```

The DefinePlugin replaces environment variables at build time. The
optimization section enables dead code elimination.

### Rollup configuration

Rollup uses plugins to transform code during the build process:

```js
import replace from '@rollup/plugin-replace';

export default {
plugins: [
replace({
'process.env.NODE_ENV': JSON.stringify('production'),
preventAssignment: true,
}),
],
};
```

The replace plugin substitutes environment variables with their
values throughout your code.

### esbuild configuration

esbuild uses a configuration object to control build behavior:

```js
{
"define": {
"process.env.NODE_ENV": "\"production\"",
"globalThis.process": "true"
},
"treeShaking": true
}
```

The define section replaces variables, and treeShaking enables
dead code elimination.

## Measure your results

You should measure your bundle size before and after these
changes to verify they're working correctly.

### Install analysis tools

Most build tools provide bundle analysis capabilities through plugins
or built-in commands. These include bundle visualizers, size reporters,
and dependency analyzers.

### Expected improvements

Bundling, tree shaking, and minification typically shrink a GraphQL.js
application's bundle noticeably. The exact reduction depends on how much of
GraphQL.js you use and how effectively your build tool eliminates unused code,
so measure your own before-and-after numbers rather than relying on a fixed
figure. In a well-configured build you should see:

- A smaller compressed bundle after tree shaking and minification.
- The development-only `instanceOf` code path removed from the output.

Disabling introspection is a separate, runtime concern rather than a build-time
optimization. See [Going to Production](./going-to-production) for how to turn it
off with a validation rule.

## Test production preparation

Follow these steps to confirm your production preparation is working correctly.

### Check environment variable replacement

Search your built files to ensure environment variables were replaced:

```bash
grep -r "process.env.NODE_ENV" your-build-directory/
```

This command should return no results. If you find unreplaced variables,
your build tool isn't performing build-time replacement correctly.

### Confirm development code removal

Add this temporary test code to your application:

```js
if (process.env.NODE_ENV !== 'production') {
console.log('This message should never appear in production');
}
```

Build your application and load it in a browser. If this console message
appears, your dead code elimination isn't working.

### Test GraphQL functionality

Deploy your prepared build to a test environment and verify:

- GraphQL queries execute successfully
- Error handling still works as expected
- Application performance improved
- No new runtime errors occur
Loading