Redocly & OpenAPI UI Configuration
Shipping enterprise API documentation with Redocly means treating your OpenAPI description as a build input: validate it, bundle it into a single deterministic file, render static HTML, and publish to a CDN — all from a pipeline that runs on every merge. This guide is part of Developer Portal Frameworks & UI Setup, and it focuses specifically on the Redocly toolchain (@redocly/cli v2.x) and the redocly.yaml configuration file that drives linting, bundling, theming, and the build-docs renderer. By the end you will have a redocly.yaml you can commit, a GitHub Actions workflow that gates merges on lint errors, and a versioned deployment layout that survives schema growth.
Redoc renders a clean, three-panel reference from a single OpenAPI file with no server-side runtime. That static model is its strength for large enterprises: the output is a self-contained HTML bundle you can host anywhere, audit for security, and cache aggressively. The trade-off is that interactive “try it” features and live request execution are limited compared with a renderer built around an embedded HTTP client. If your teams need an inline request runner, pair Redoc reference pages with Scalar & Modern Docs Integration for the sections that demand it, or compare the two renderers head to head in the Redoc vs Scalar comparison.
Prerequisites & Environment Setup
You need Node.js 18 or newer (20 LTS recommended), an OpenAPI 3.0 or 3.1 description, and a CDN or static host with credentialed CI access. Install the CLI as a dev dependency rather than globally so the version is pinned in your lockfile and reproducible across machines and CI runners:
npm install --save-dev @redocly/cli@^2
npx @redocly/cli --version # expect 2.x.x
Pinning matters because build-docs embeds a specific Redoc renderer version inside the generated HTML. If you let the CLI float, a transitive renderer bump can shift layout or class names between builds with no change to your spec. Commit package-lock.json, and treat a CLI upgrade as a reviewed change with a visual diff.
If you previously used the standalone redoc-cli package, remove it. It is deprecated and no longer maintained; @redocly/cli is the supported successor. The command mapping is direct: redoc-cli bundle becomes redocly build-docs, and you gain a real linter (redocly lint) and bundler (redocly bundle) in the same binary.
Initialise a configuration file at the repository root. The CLI auto-discovers redocly.yaml in the working directory, so keep it beside your spec:
touch redocly.yaml
Decide your deployment topology before writing CI. The cleanest model for enterprises is one CDN path prefix per semantic version (for example s3://api-docs/v1.2.0/) with a stable alias such as /latest/ updated on each release. Lock that in now so the workflow’s deploy step has a predictable target.
Core Configuration
redocly.yaml is the single source of truth for linting rules, theme tokens, and per-API definitions. The top-level keys you will use most are extends, rules, theme, and apis. Here is an annotated configuration you can adapt:
# redocly.yaml — repository root, beside openapi.yaml
extends:
- recommended # baseline ruleset shipped with @redocly/cli
# Register named API definitions so you can target them by alias:
# `redocly lint core@v1` instead of a file path.
apis:
core@v1:
root: ./specs/core/openapi.yaml
core@v2:
root: ./specs/core/openapi-v2.yaml
rules:
no-unused-components: error # dead schemas fail the build
operation-operationId: error # every operation needs a stable id (deep links, SDKs)
operation-summary: warn # nudge, do not block, on missing summaries
info-contact: warn
no-server-example.com: error # catch placeholder server URLs before publish
security-defined: error # every operation must reference a declared scheme
theme:
openapi:
# Compiled into static HTML at build time — no runtime injection.
theme:
colors:
primary:
main: '#16306d'
typography:
fontFamily: 'Inter, system-ui, sans-serif'
fontSize: '15px'
hideDownloadButton: false
expandResponses: '200,201' # auto-expand success responses
sortPropsAlphabetically: false # preserve authored property order
sidebar:
backgroundColor: '#F8FAFC'
A few rules deserve emphasis. Set operation-operationId: error early — operationId is what powers deep links into the rendered page and stable method names in generated SDKs, and it is painful to retrofit once consumers depend on anchors. security-defined: error prevents shipping an operation whose auth requirements are undocumented, which is a common gap in fast-moving teams. The recommended preset covers structural correctness; everything in your rules block above it is governance you are choosing to enforce.
The theme.openapi.theme block compiles into the HTML. There is no live CSS injection, so any value that references a CSS custom property must have that property defined in a stylesheet loaded before the Redoc bundle. For multi-mode theming, define dark tokens in an external stylesheet keyed on [data-theme='dark'] or prefers-color-scheme and let the page shell toggle the attribute — the full pattern lives in Multi-Theme & Dark Mode Support.
Integration Pattern
The pipeline below lints, bundles, renders, and publishes a versioned artifact. It triggers only when the spec or config changes, so unrelated commits do not rebuild docs. The lint step runs first and fails the job on any error-severity violation, which is the gate that keeps broken specs out of production.
# .github/workflows/docs-deploy.yml
name: Build & Deploy API Docs
on:
push:
branches: [main]
paths:
- 'specs/**'
- 'redocly.yaml'
permissions:
contents: read
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
- name: Lint spec (CI gate)
run: npx @redocly/cli lint core@v1 --format=github
- name: Bundle to a single deterministic file
run: npx @redocly/cli bundle core@v1 --output dist/bundle.yaml
- name: Render static docs
run: >-
npx @redocly/cli build-docs dist/bundle.yaml
--output dist/index.html
- name: Deploy versioned prefix
run: aws s3 sync dist/ s3://api-docs-bucket/v1.2.0/ --delete
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Update stable alias
run: aws s3 sync dist/ s3://api-docs-bucket/latest/ --delete
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
The --format=github flag turns lint violations into inline annotations on the pull request diff, so reviewers see problems in context. The --delete flag on s3 sync removes stale assets from the prefix, guaranteeing the CDN serves only the current build. Publishing to both a versioned prefix and a latest/ alias gives consumers a permanent URL per release plus a stable entry point, with no extra build cost.
For a pull-request preview, duplicate the job on pull_request, drop the deploy steps, and upload dist/ with actions/upload-artifact so reviewers can download and open the rendered docs before merge.
Advanced Options
Split a large spec, bundle for delivery. Author with $ref across many files for maintainability, then collapse to one file at build time. redocly bundle preserves internal $refs by default; pass --dereferenced to inline everything, which sidesteps renderers that choke on references but produces a larger file. Reserve full dereferencing for specs whose recursion breaks the default bundler.
Decorators and custom rules. Beyond the built-in ruleset, redocly.yaml accepts decorators to transform the spec during bundling — for example, stripping internal-only operations marked with x-internal: true before publishing a public artifact. Combine a decorator with a separate config profile so the same source spec produces both an internal and a public build. For governance at scale, author custom lint rules; the broader practice is covered in Spec Linting & Governance and Writing custom Spectral rules.
Performance for very large schemas. Specs with thousands of operations slow the initial render. Use expandResponses sparingly, enable sortPropsAlphabetically: false to skip a sort pass, and consider splitting one monolithic reference into per-domain pages. Enterprise-grade tuning, SSO-gated hosting, and search are detailed in Configuring Redoc for enterprise API documentation.
Verification & Testing
Verify the build the way CI does, locally, before you push. Run the same three commands and confirm each exit code:
npx @redocly/cli lint core@v1
echo "lint exit: $?" # 0 = clean or warnings only; 1 = errors
npx @redocly/cli bundle core@v1 --output dist/bundle.yaml
test -f dist/bundle.yaml && echo "bundle OK"
npx @redocly/cli build-docs dist/bundle.yaml --output dist/index.html
test -s dist/index.html && echo "render produced non-empty HTML"
The linter exits 1 on error-severity violations and 0 when only warnings remain, which is exactly what makes it a safe CI gate. To preview the rendered output interactively, use the dev server, which hot-reloads on spec edits:
npx @redocly/cli preview-docs core@v1
# serves on http://localhost:8080
Finally, smoke-test the deployed artifact. Fetch the published index.html and grep for a known operation summary or your API title to confirm the deploy step actually replaced the previous build rather than failing silently:
curl -s https://docs.example.com/latest/ | grep -q "Your API Title" \
&& echo "deploy verified" || echo "deploy MISSING content"
Troubleshooting
Circular dependency detected during bundle. Recursive schemas (a type that references itself, directly or via a chain) can break naive flattening. Keep references intact with the default redocly bundle, or if a downstream tool requires a flat file, run redocly bundle --dereferenced and accept the larger output. Do not hand-edit the spec to remove legitimate recursion.
Cannot resolve $ref / Could not resolve pointer. A $ref points at a missing file, a renamed component, or a typo’d JSON pointer. Run redocly lint — it reports the exact file and line. The most common cause is a relative path that resolves correctly from one file’s directory but not from the bundle root; prefer references relative to the file containing them.
Theme overrides silently ignored. Custom CSS targeting Redoc’s internal class names (such as .redoc-wrap) is fragile and frequently lost on a renderer bump, because those class names are not a stable API. Move every supported change into the theme.openapi.theme block in redocly.yaml, and restrict raw CSS to layout tweaks the theme API genuinely cannot express.
Layout shifts after a CLI upgrade. Because build-docs embeds the renderer version, bumping @redocly/cli can change the output even with an identical spec. Pin the CLI, review the changelog before upgrading, and run a visual diff of the rendered index.html as part of the upgrade PR.
FAQ
Does Redocly render OpenAPI 3.1 specs?
Yes. @redocly/cli v2.x validates, bundles, and renders OpenAPI 3.1 specs including webhooks and JSON Schema 2020-12 keywords. Check the Redoc release notes for the status of newer 3.1 constructs such as unevaluatedProperties.
What replaced the deprecated redoc-cli package?
The standalone redoc-cli package is end-of-life and superseded by @redocly/cli. Use redocly build-docs in place of redoc-cli bundle, and redocly lint in place of any separate validation step.
How do I host multiple API versions with Redocly?
Build one static artifact per version, each written to its own CDN path prefix such as /v1/ and /v2/. Add a routing layer or a portal landing page that links to each version, and redirect deprecated paths to the current default.
Can I theme Redoc at runtime instead of build time?
No. The theme.openapi block in redocly.yaml is compiled into the static HTML at build time, so there is no runtime CSS injection. For per-user dark mode, ship a separate stylesheet keyed on a data-theme attribute or prefers-color-scheme.
Related
- Developer Portal Frameworks & UI Setup — parent overview of renderer and portal choices.
- Configuring Redoc for enterprise API documentation — SSO, search, and large-schema tuning.
- Scalar & Modern Docs Integration — add an embedded HTTP client where Redoc’s static model falls short.
- Swagger UI Customization — compare against the interactive plugin-based renderer.
- Multi-Theme & Dark Mode Support — token-based theming across light and dark.