improving syntax highlighting in my blog using shiki

October 22, 2025

Previously, my blog used a custom code renderer with sugar-high for syntax highlighting (from the Vercel template this site started from). However, I ran into formatting and display issues and accessibility concerns, so I switched to Shiki using the official @shikijs/rehype plugin.

This tutorial will be easiest to follow if you started from the same Vercel portfolio starter kit template. (or a similiar one) as that I used.

How to use Shiki in Next.js (MDX)

1. Install the package

First, install shiki and the rehype plugin (and remove sugar-high if you still have it):

npm uninstall sugar-high
npm install shiki @shikijs/rehype

2. Update your MDX renderer

import { MDXRemote } from 'next-mdx-remote/rsc'
import rehypeShiki from '@shikijs/rehype'

export function CustomMDX(props) {
  return (
    <MDXRemote
      {...props}
      components={/* your custom components (if you have any) */}
      options={{
        mdxOptions: {
          rehypePlugins: [[rehypeShiki, {
            // pick a bundled theme; I use a darker, higher-contrast one
            theme: 'material-theme-darker',
            // optionally set languages and aliases
            langs: ['tsx', 'typescript', 'bash', 'css', 'yaml', 'hcl'],
            langAlias: { ts: 'typescript', js: 'javascript', sh: 'bash' },
          }]],
        },
      }}
    />
  )
}

3. Remove highlight.js CSS if present

If you previously used Highlight.js, remove any highlight.js/styles/*.css imports. Shiki inlines token colors, so you don't need a global theme stylesheet.

4. Remove old sugar-high styles from global.css (if any)

If you started from the same template, you might have --sh-* CSS variables. Remove those to avoid confusion with Shiki's output. For reference, this is what I removed initially:

:root {
  --sh-class: #2d5e9d;
  --sh-identifier: #354150;
  --sh-sign: #8996a3;
  --sh-string: #007f7a;
  --sh-keyword: #e02518;
  --sh-comment: #a19595;
  --sh-jsxliterals: #6266d1;
  --sh-property: #e25a1c;
  --sh-entity: #e25a1c;
}

@media (prefers-color-scheme: dark) {
  :root {
    --sh-class: #4c97f8;
    --sh-identifier: white;
    --sh-keyword: #f47067;
    --sh-string: #0fa295;
  }
  html {
    color-scheme: dark;
  }
}

.prose pre {
  @apply bg-neutral-50 dark:bg-neutral-900 rounded-lg overflow-x-auto border border-neutral-200 dark:border-neutral-900 py-2 px-3 text-sm;
}

.prose code {
  @apply px-1 py-0.5 rounded-lg;
}

.prose pre code {
  @apply p-0;
  border: initial;
  line-height: 1.5;
}

.prose code span {
  @apply font-medium;
}

.prose p {
  @apply my-4 text-neutral-800 dark:text-neutral-200;
}

Results

Code blocks now render consistently on the server with better readability, and I can control themes and accessibility centrally.

Conclusion

Switching to Shiki solved the formatting and display issues I had with the default renderer and gives me more control over accessibility.