Configuring Redoc for Enterprise API Documentation
Enterprise Redoc deployments fail in predictable ways: non-reproducible builds, missing security headers, no authentication layer, and multi-second first renders on large specs. This guide is part of Redocly & OpenAPI UI Configuration and the broader Developer Portal Frameworks & UI Setup section. It gives exact @redocly/cli commands, an annotated redocly.yaml, an Express security wrapper, and the performance knobs that matter once a spec passes a few hundred operations.
This scenario arises when a public-facing reference graduates to an internal, access-controlled portal that compliance reviews — at which point ad-hoc redoc-cli invocations and an unhardened static host stop being acceptable.
Problem & Context
A typical broken state: docs are built locally with the deprecated redoc-cli, pushed to an S3 bucket with no headers, and reachable by anyone with the URL. The build is not reproducible because nobody pinned the CLI, so two engineers produce different HTML from the same spec. Compliance flags the missing Content-Security-Policy and Strict-Transport-Security, and the page takes four seconds to become interactive because the renderer resolves every $ref at runtime. Each of these is solvable with configuration rather than code changes.
The goal is a deterministic pipeline: one pinned CLI, one config file, a hardened serving layer, and a bundle that the browser can paint quickly.
Step-by-Step Solution
1. Install the supported CLI
Use @redocly/cli, not the deprecated redoc-cli, and add it as a dev dependency so CI installs the exact tree from your lockfile:
npm install --save-dev @redocly/cli
2. Lint, bundle, and build in one chain
npx @redocly/cli lint openapi.yaml
npx @redocly/cli bundle openapi.yaml --output dist/bundle.yaml --dereferenced
npx @redocly/cli build-docs dist/bundle.yaml --output dist/index.html --config redocly.yaml
Expected output on success:
Linting openapi.yaml...
No errors or warnings.
Bundling openapi.yaml...
dist/bundle.yaml: bundle created.
Building docs from dist/bundle.yaml...
dist/index.html: static docs created.
The --dereferenced flag flattens every $ref at build time, which both speeds the first render and sidesteps the circular-reference crash described below.
3. Drive all theming from redocly.yaml
The theme.openapi block maps directly to Redoc’s React theme object. Annotated config:
# redocly.yaml
extends:
- recommended
rules:
no-unused-components: error # fail the build on dead components
operation-summary: warn # nudge authors to summarize every operation
theme:
openapi:
disableTryItButton: false # set true to hide the console in read-only prod
showExtensions: false # hide x- vendor extensions from readers
lazyRendering: true # defer off-screen operations for large specs
colors:
primary:
main: "#0052CC"
typography:
fontFamily: "Inter, system-ui, sans-serif"
fontSize: "15px"
sidebar:
backgroundColor: "#F8FAFC"
Inject custom CSS through --theme.openapi.customCSS or a linked stylesheet rather than overriding internal Redoc class names — those change between minor versions.
4. Harden the serving layer with security headers
Redoc ships no authentication or header layer, so wrap the static output:
// server.js
const express = require('express');
const path = require('path');
const app = express();
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
);
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
next();
});
app.use(express.static(path.join(__dirname, 'dist')));
app.listen(3000);
'unsafe-inline' is required by Redoc’s generated CSS. If your policy forbids it, switch to a nonce-based CSP and inject the nonce into the HTML template during the build step.
5. Add SSO in front of the static site
Redoc has no login layer, so place the static docs behind an authentication proxy — OAuth2 Proxy, Cloudflare Access, or AWS Cognito with CloudFront. The proxy authenticates the request before the static asset is served; Redoc never sees credentials.
6. Tune for large schemas
# Flatten refs and pre-build so the browser does no runtime resolution
npx @redocly/cli bundle openapi.yaml --output dist/bundle.yaml --dereferenced
Beyond lazyRendering: true, strip example payloads from the production spec to shrink the bundle, and pre-compress the static output with Brotli where your web server supports it.
Complete Working Example
A single GitHub Actions workflow that pins the CLI through npm ci, runs the full chain, and publishes to S3:
# .github/workflows/docs.yml
name: Build API Docs
on:
push:
branches: [main]
paths: ['openapi.yaml', 'redocly.yaml']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npx @redocly/cli lint openapi.yaml
- run: npx @redocly/cli bundle openapi.yaml --output dist/bundle.yaml --dereferenced
- run: npx @redocly/cli build-docs dist/bundle.yaml --output dist/index.html --config redocly.yaml
- run: aws s3 sync dist/ s3://api-docs-bucket/latest/ --delete
Keep redocly.yaml from the steps above in the repo root so both local builds and CI produce byte-identical HTML.
Gotchas & Edge Cases
Maximum call stackduring build. Circular$refchains overflow the resolver. Bundle with--dereferencedto flatten references beforebuild-docsever runs.- CORS preflight failures in Try It. The browser blocks cross-origin requests from the docs domain to the API. Either enable CORS on the API for the docs origin, or set
disableTryItButton: truein production and route developers to a sandbox. - Theme not applied after deploy. The theme is compiled into the static HTML at build time, so a
redocly.yamlchange does nothing until you rebuild. Any CSS variables referenced by the theme object must be defined in the page stylesheet before the bundle loads.
FAQ
How do I handle OpenAPI 3.1 vs 3.0 compatibility in Redoc?
@redocly/cli v2.x validates OpenAPI 3.1 fully, while the Redoc renderer has only partial 3.1 support — webhooks and some JSON Schema 2020-12 keywords may render with fallback behavior. Check the Redoc changelog for the current 3.1 feature status before relying on 3.1-specific constructs in a customer-facing reference.
Can I conditionally render endpoints based on user roles?
Redoc has no runtime role-based filtering. Use @redocly/cli split or a custom script to produce role-specific spec files, then build a separate static bundle per audience and route each behind the appropriate access policy in your SSO proxy.
Why does the Redoc bundle exceed 5 MB?
The usual causes are large inline example values, base64-encoded images in x-logo, and unminified payloads in examples blocks. Strip those fields from the production spec — for instance yq del '.paths[][][].requestBody.content.*.example' openapi.yaml — before passing it to build-docs. Managing big payloads is covered in Managing Large Example Payloads in API Specs.