You will assemble a complete production configuration: transformers, dual themes, selected langs, complete CSS styling, and an SSG pipeline, then set up a CI/CD and maintenance strategy for controlled dependency upgrades.

In the previous episodes you learned features separately. Now it's time to combine everything into one production-ready configuration: plugins, transformers, dual themes, selected langs, CSS styling, and a consistent build pipeline.
Episode 21 assembles the production-ready configuration: the final rehype-pretty-code config, complete CSS styling for all code block elements, the SSG build pipeline, plus CI/CD and maintenance practices to keep the system stable when dependencies are upgraded.
The production configuration combines transformers, dual themes, and a selective langs list:
import {
transformerNotationDiff,
transformerNotationHighlight,
} from "@shikijs/transformers";
const rehypePrettyCodeOptions = {
theme: {
dark: "github-dark-default",
light: "github-light-default",
},
defaultLang: "plaintext",
keepBackground: false,
langs: ["ts", "tsx", "js", "css", "html", "json", "bash"],
transformers: [
transformerNotationDiff(),
transformerNotationHighlight(),
],
};The langs list is limited to the languages that actually appear in the content, the theme uses the dual themes from episode 15, and the transformers add diff and highlight notation. This configuration becomes the single source of truth for all pages.
Register the configuration on the pipeline used by all content:
unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypePrettyCode, rehypePrettyCodeOptions)
.use(rehypeSanitize)
.use(rehypeStringify);The plugin order follows the principle from episode 17: highlight before sanitization, sanitization before stringify. This pipeline is used once in the build config, not per page, so behavior stays consistent.
A production code block needs styling for the title, highlighted lines, diff, and background. The following example summarizes it in one set of rules:
[data-rehype-pretty-code-figure] {
position: relative;
}
[data-rehype-pretty-code-figure] pre {
background: #ffffff;
border-radius: 8px;
overflow-x: auto;
}
[data-rehype-pretty-code-figure] span {
color: var(--shiki-light);
}
[data-rehype-pretty-code-figure] [data-highlighted-line] {
background: rgba(255, 200, 0, 0.12);
}
@media (prefers-color-scheme: dark) {
[data-rehype-pretty-code-figure] pre {
background: #0d1117;
}
[data-rehype-pretty-code-figure] span {
color: var(--shiki-dark);
}
}These rules handle the background for both modes, the token colors via CSS variables, and line highlighting. Add the data-added-line and data-removed-line styling from episode 8 to complete the diff.
Keep the styling in a separate CSS file so it doesn't mix with component logic. In Next.js, just import that file in the layout or a global file. This separation makes swapping palettes easy without touching component code.
Make sure the build is deterministic: the same result for the same input. Every document goes through the same pipeline, and static assets are produced at build time. Before merging to production, run a full build:
bun run buildThis command runs the content build then the Next.js build in sequence with bun run build. If the build fails, the CI pipeline fails too, so problems are caught before reaching users.
Snapshotting the HTML output of a few key pages prevents unexpected visual changes. Save snapshots after the build and compare them on every change. If a snapshot changes, check whether it's an intended change. This catches subtle regressions that don't appear as errors.
Don't jump straight to the newest version without testing. Review the changelog first, then perform the upgrade on a separate branch and run the build and snapshots:
bun add shiki@^4 rehype-pretty-code@^0.14After upgrading, run all the tests and compare the snapshots. Constrain dependencies with a narrow range so accidental updates don't break the rendered output.
Watch the build duration and bundle size on every release. A sharp increase indicates a new grammar or asset accidentally entering the bundle. Integrate a size warning into the CI pipeline so performance regressions are detected early.
Key takeaways:
In episode 22 you'll close the series with the alternative ecosystem and a final reflection: comparing Shiki with highlight.js, Prism, and lowlight, plus a recap of the journey from episode 0 to 21 into a production-grade code block checklist.