Modern Syntax Highlighting with Shiki in Next.js 14
, 6m read
Learn how to implement Shiki for modern and performant syntax highlighting in your React and Next.js projects with this comprehensive step-by-step guide.
By Nikolai Lehbrink
Web enthusiast, specialising in the React ecosystem.
For integrating code blocks on this website I was looking for a modern approach to syntax highlighting and came across Shiki
, a pretty new syntax highlighter created by Pine Wu and maintained and rewritten by Anthony Fu. It is used by big companies like Vercel for their Next.js docs or Astro for their syntax highlighting. As I couldn't find a guide myself for the app router of Next.js 13 and above, I thought I'll write one by myself.
What we build
Let's have a look at what we are going to build today. A code block with syntax highlighting that enables us to also highlight certain lines and even show code diffs. The following code block is an example.
The final code
If you are just interested in the final outcome, you can find the code in my repository nikolailehbrink/shiki-next.
There I also added the „Copy to Clipboard“ functionality, which I am not going to talk about in this blog post because I felt it was out of scope.
Why Shiki?
Choosing Shiki
for syntax highlighting in React and especially Next.js apps can be a smart move for many reasons. First, Shiki
is based on TextMate grammars, the same system used by Visual Studio Code, ensuring accurate and visually appealing code highlighting. More importantly for the performance of our website, Shiki
renders highlighted code ahead of time. This means no extra JavaScript is shipped to the client for syntax highlighting, aligning with Next.js's push for server components and minimal client-side JavaScript.
Faster load times, improved SEO, and a seamless developer experience without sacrificing the quality of our code's presentation.
Project setup
The easiest way to get started with Next.js is by using create-next-app
. This CLI tool enables you to quickly start building a new Next.js application, with everything set up for you.
Install Shiki
In your projects folder, open the terminal and add shiki as a dev dependency with the following command:
Create the code component
Now that we have installed everything we can integrate shiki
. So let's create a new folder called components
in which we create a new file called Code.tsx
which will be our code server component. Let's add a basic structure for this functional component.
Integrate shiki
In order to make the syntax highlighting work, we have to use the codeToHtml
function that shiki
provides. Because of the asynchronous nature of this function we have to refactor the code to make the component an async
function. Then call the codeToHtml
function with the code that should be formatted, the language and the theme as an argument. Lastly we set the inner HTML of the <div>
to the rendered HTML from shiki
with dangerouslySetInnerHTML
:
Display component
Now open your root page app/page.tsx
, delete all of it's initial content and add the code component to the page.
When running the development server (typically on localhost:3000
),we see a code block with syntax highlighting applied, which is rendered on the server.
Pretty cool and easy right?
Make component reusable
But we don't stop there, because right now everything is hard-coded and that's not the purpose of this tutorial. So let's extend our component to take the code
, the language (lang
) and the theme
as props
.
Then inside our page.tsx
we can call the Code component and pass down the props
. Also note that thanks to TypeScript we now have autosuggestions by the editor for the lang
and theme
props.
When opening the page you should see three blocks with different syntax highlighting applied (I adjusted my font size heavily for the following images):
Enhancing the component
The basic functionality is done, now let's add some enhancements to our code block.
Highlighting Specific Lines
One thing that I definitely didn't want to miss out on, was an implementation of line highlighting. Shiki
has a few transformers
that let us easily set this up.
First, install the common transformers package for Shiki
:
Then import the transformerNotationHighlight
function:
Add a comment like // [!code highlight]
in the code
prop to add a highlighted
class to the rendered HTML.
If you inspect your code now in the Developer Tools you can see that the highlighted
class was added to that specific <span>
:
Now we can target the class with CSS and style it to our liking. I will demonstrate it in a simple way with TailwindCSS and it's arbitrary values.
Now your code block should indicate the highlighted line with a blue background.
This isn't the prettiest code block yet, but we will focus on styling in the end. Let's look at a different transformer
, with which we can show code changes with, which you might have seen on sites like GitHub.
Showing Code Changes
To add classes for added and removed lines, we follow the same steps as above, but with a different transformer called transformerNotationDiff
. First import the transformer
.
Then add the // [!code ++]
comment for added lines and a respective // [!code --]
comment for removed lines to your code
prop.
This again adds the diff remove
and diff add
classes on the rendered HTML that we can use for styling the component later.
Including Filenames
Besides the highlighting I wanted to add filenames to my code components. That way the reader always knows to which file the given code belongs to.
So let's add a filename
prop to our component and some initial styling to see the effect.
In our page.tsx
let's add filenames by passing a name to the filename
prop.
Now we should see the filenames on top of the code block.
Customizing appearance
We have all the functionality implemented, so we can move over to the fun part and style our code block.
Integrate Line Numbers
One common thing to integrate in a code block are line numbers, making the component visually more appealing while also giving the reader a reference for specific lines in the code.
In Issue #3 from Shiki this was discussed and a user called Alex Peattie presented a pretty awesome CSS solution to line numbers in shiki.
I refactored the code from the solution to TailwindCSS utility classes in app/globals.css
.
To make the nested css work, we have to add the tailwindcss/nesting
in our postcss.config.js
.
You don't have to install the plugin as it already comes packed with TailwindCSS which was preinstalled via create-next-app
.
Now the code block should have line numbers:
Styling Highlights and Diffs
In order to enhance the design of the individual highlighted lines, I added the following styles:
Final Touch
Lastly, I added some styles to make the whole component more visually appealing and added a background gradient.
Let's switch once again to our page.tsx
, remove all three code blocks and add a new one with more than one line of code. Add some comments like // [!code highlight]
, // [!code ++]
or // [!code --]
on different lines to see the styles applied.
You should now see the final code block.
Bonus: Copy to clipboard functionality
I also incorporated a „Copy to Clipboard“ button into the code components on this site. If you're interested in how to implement this feature, you can check out the complete code on Github.
The commits in the repository chronologically represent the steps outlined in this tutorial.