February 8, 2024 ยท 4 min read ยท 2,037 views
The article was updated to make a singleton highlighter, as mentioned in the latest Shiki update, to resolve certain memory leaks when the highlighter is called from React Server Components. Here's a list of the updates:
Updated getHighlighter
to be a singleton with makeSingletonHighlighter
Removed custom Blade language as now it's bundled with Shiki!
Wouldn't it be wonderful if we could deliver syntax highlighting to our readers without adding any additional JavaScript weight? That's precisely what we'll accomplish using Shiki, React Server Components (RSC), and Next.js.
First, we need to install Shiki:
npm install -D shiki
Next, we'll create a React Server Component. In Next.js's App Router, Server Components are the default:
export default async function Code({ code, language }) {
return <div></div>;
}
From the props in our component, we take a code
and a language
. The code
prop might come from a Content Management System (CMS) and typically contains the HTML code block for our code examples. The language
prop indicates which programming language the syntax highlighter should use.
To keep our code clean, we will wrap the syntax highlighting logic in a helper function. Go ahead and make a file called shiki.js inside the utils folder, and add the following content:
import { createHighlighter, makeSingletonHighlighter } from 'shiki';
import { bundledLanguages } from 'shiki/bundle/web';
import antlers from '../../content/languages/antlers.json';
const getHighlighter = makeSingletonHighlighter(createHighlighter);
export const codeToHtml = async ({ code, language }) => {
const highlighter = await getHighlighter({
themes: ['github-light', 'github-dark'],
langs: [
...Object.keys(bundledLanguages),
{
id: 'antlers',
scopeName: 'text.html.statamic',
embeddedLangs: ['html'],
...antlers,
},
],
});
return highlighter.codeToHtml(code, {
lang: lang,
themes: {
dark: 'github-dark',
light: 'github-light',
},
});
};
Let's break this down:
We import the web bundle from Shiki to minimize the load. If you need support for additional languages, you can import the full bundle without worrying about client-side load since this occurs server-side.
We use the getHighlighter
function from Shiki to configure our highlighter with additional features before performing the syntax highlighting.
We've set up two themes for light and dark modes. If your site doesn't flip between modes, you can just stick with one theme.
Shiki has built-in support for a lot of themes. Feel free to check the list.
const highlighter = await getHighlighter({
theme: 'github-dark',
});
We also include support for custom languages. For example, we've added Antlers for our Statamic ( Hey Statamic friends ๐ ) audience. We sourced Antlers from this repository.
At the bottom of our JavaScript helper, we export the highlighter and the codeToHtml
function, which accepts our language and themes and returns syntax-highlighted HTML.
return highlighter.codeToHtml(code, {
lang: lang,
themes: {
dark: 'github-dark',
light: 'github-light',
},
});
With our helper function ready, let's integrate it into our React Server Component:
import { codeToHtml } from '@/utils/shiki';
export default async function Code({ code, language }) {
const html = await codeToHtml({
code,
language,
});
return <div className="px-5" dangerouslySetInnerHTML={{ __html: html }} />;
}
With our component and syntax highlighting in place, we can enhance the user experience by incorporating CSS styles that respect the user's theme preferences. This step is optional but recommended if your site supports light and dark modes.
To implement theme-based rendering, you'll need to have TailwindCSS configured with darkMode
set to class
.
Here's an example of how you can define your CSS to switch between themes:
html.dark .shiki,
html.dark .shiki span {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
/* Optional, if you also want font styles */
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
/* Optional, for the code block we add overflow, borders and padding */
.shiki {
@apply overflow-x-auto rounded-xl p-5;
}
By adding these styles to your CSS file and ensuring that your site's HTML includes the appropriate classes, you can provide a seamless and visually appealing experience for users, whether they prefer light or dark mode.
Remember to test your styles to ensure that they switch correctly based on the user's preference and that the syntax highlighting remains legible and attractive in both themes.
And there you have it! We've successfully set up beautiful, syntax-highlighted code blocks without shipping any extra JavaScript to our readers. Enjoy the seamless integration of Shiki, React Server Components, and Next.js in your projects.
If you need help with a Laravel project let's get in touch.
Lucky Media is proud to be recognized as a Top Next.js Development Agency
Technologies:
Related Posts
Stay up to date
Be updated with all news, products and tips we share!