icon

How to Highlight Code Syntax with PrismJS Statically in Next.js with Dark and Light Themes

Learn how to add static code block syntax highlighting using PrismJS in Next.js, with support for both dark and light themes.

thumbnail image
6 min
Posted by :Sujal
Published on
Updated on

In this tutorial, we'll walk through how to integrate PrismJS for code block syntax highlighting in a Next.js app with dark and light theme support. You'll need a Next.js app with the App Router and Tailwind CSS already installed.

Step 1: Install PrismJS

First, install PrismJS by running the following command:

Bash
Copy
npm i prismjs

Step 2: Create the Code Component

In the src folder, create a components folder. If you don't have a src folder, create one in the root directory. Inside the components folder, create a code.jsx file and paste the following code:

JavaScript
Copy
import Prism from "prismjs";

import "prismjs/components/prism-abap";
import "prismjs/components/prism-agda";
// using NASM for highlighting Assembly code
import "prismjs/components/prism-nasm";
import "prismjs/components/prism-bash";
import "prismjs/components/prism-basic";
import "prismjs/components/prism-bnf";
import "prismjs/components/prism-c";
import "prismjs/components/prism-csharp";
import "prismjs/components/prism-cpp";
import "prismjs/components/prism-clojure";
import "prismjs/components/prism-coffeescript";
import "prismjs/components/prism-coq";
import "prismjs/components/prism-css";
import "prismjs/components/prism-dart";
import "prismjs/components/prism-dhall";
import "prismjs/components/prism-diff";
import "prismjs/components/prism-docker";
import "prismjs/components/prism-ebnf";
import "prismjs/components/prism-elixir";
import "prismjs/components/prism-elm";
import "prismjs/components/prism-erlang";
import "prismjs/components/prism-fsharp";
import "prismjs/components/prism-flow";
import "prismjs/components/prism-fortran";
import "prismjs/components/prism-gherkin";
import "prismjs/components/prism-glsl";
import "prismjs/components/prism-go";
import "prismjs/components/prism-graphql";
import "prismjs/components/prism-groovy";
import "prismjs/components/prism-haskell";
import "prismjs/components/prism-idris";
import "prismjs/components/prism-java";
// using jsx instead of javascript
import "prismjs/components/prism-javascript";
import "prismjs/components/prism-jsx";
import "prismjs/components/prism-json";
import "prismjs/components/prism-julia";
import "prismjs/components/prism-kotlin";
import "prismjs/components/prism-latex";
import "prismjs/components/prism-less";
import "prismjs/components/prism-lisp";
import "prismjs/components/prism-livescript";
import "prismjs/components/prism-llvm";
import "prismjs/components/prism-lua";
import "prismjs/components/prism-makefile";
import "prismjs/components/prism-markdown";
import "prismjs/components/prism-markup";
import "prismjs/components/prism-matlab";
// using Wolfram for highlighting Mathematica code
import "prismjs/components/prism-wolfram";
import "prismjs/components/prism-mermaid";
import "prismjs/components/prism-nix";
import "prismjs/components/prism-objectivec";
import "prismjs/components/prism-ocaml";
import "prismjs/components/prism-pascal";
import "prismjs/components/prism-perl";
import "prismjs/components/prism-powershell";
import "prismjs/components/prism-prolog";
import "prismjs/components/prism-protobuf";
import "prismjs/components/prism-purescript";
import "prismjs/components/prism-python";
import "prismjs/components/prism-r";
import "prismjs/components/prism-reason";
import "prismjs/components/prism-ruby";
import "prismjs/components/prism-rust";
import "prismjs/components/prism-sass";
import "prismjs/components/prism-scala";
import "prismjs/components/prism-scheme";
import "prismjs/components/prism-scss";
import "prismjs/components/prism-solidity";
import "prismjs/components/prism-sql";
import "prismjs/components/prism-swift";
import "prismjs/components/prism-toml";
import "prismjs/components/prism-typescript";
import "prismjs/components/prism-vbnet";
import "prismjs/components/prism-verilog";
import "prismjs/components/prism-vhdl";
import "prismjs/components/prism-visual-basic";
import "prismjs/components/prism-wasm";
import "prismjs/components/prism-xml-doc";
import "prismjs/components/prism-yaml";
import "prismjs/components/prism-racket";
import "prismjs/components/prism-markup-templating";
import "prismjs/components/prism-php";
import "prismjs/components/prism-arduino";

// Language capitalization mapping
const languageMapping = {
  abap: "ABAP",
  agda: "Agda",
  arduino: "Arduino",
  assembly: "Assembly",
  bash: "Bash",
  basic: "BASIC",
  bnf: "BNF",
  c: "C",
  "c#": "C#",
  "c++": "C++",
  clojure: "Clojure",
  coffeescript: "CoffeeScript",
  coq: "Coq",
  css: "CSS",
  dart: "Dart",
  dhall: "Dhall",
  diff: "Diff",
  docker: "Docker",
  ebnf: "EBNF",
  elixir: "Elixir",
  elm: "Elm",
  erlang: "Erlang",
  "f#": "F#",
  flow: "Flow",
  fortran: "Fortran",
  gherkin: "Gherkin",
  glsl: "GLSL",
  go: "Go",
  graphql: "GraphQL",
  groovy: "Groovy",
  haskell: "Haskell",
  html: "HTML",
  idris: "Idris",
  java: "Java",
  javascript: "JavaScript",
  json: "JSON",
  julia: "Julia",
  kotlin: "Kotlin",
  latex: "LaTeX",
  less: "Less",
  lisp: "Lisp",
  livescript: "LiveScript",
  "llvm ir": "LLVM IR",
  lua: "Lua",
  makefile: "Makefile",
  markdown: "Markdown",
  markup: "Markup",
  matlab: "MATLAB",
  mathematica: "Mathematica",
  mermaid: "Mermaid",
  nix: "Nix",
  "notion formula": "Notion Formula",
  "objective-c": "Objective-C",
  ocaml: "OCaml",
  pascal: "Pascal",
  perl: "Perl",
  php: "PHP",
  "plain text": "Plain Text",
  powershell: "PowerShell",
  prolog: "Prolog",
  protobuf: "Protobuf",
  purescript: "PureScript",
  python: "Python",
  r: "R",
  racket: "Racket",
  reason: "Reason",
  ruby: "Ruby",
  rust: "Rust",
  sass: "Sass",
  scala: "Scala",
  scheme: "Scheme",
  scss: "SCSS",
  shell: "Shell",
  solidity: "Solidity",
  sql: "SQL",
  swift: "Swift",
  toml: "TOML",
  typescript: "TypeScript",
  "vb.net": "VB.Net",
  verilog: "Verilog",
  vhdl: "VHDL",
  "visual basic": "Visual Basic",
  webassembly: "WebAssembly",
  xml: "XML",
  yaml: "YAML",
};

export default function Code({ block }) {

  const codeContent = block.code.rich_text[0].plain_text;
  const language = block.code.language;
  const displayLanguage = languageMapping[language] || language;

  // Check if the language exists in Prism.languages
  const prismLanguage =
    language === "javascript"
      ? Prism.languages.jsx
      : language === "assembly"
      ? Prism.languages.nasm
      : language === "c#"
      ? Prism.languages.csharp
      : language === "c++"
      ? Prism.languages.cpp
      : language === "f#"
      ? Prism.languages.fsharp
      : language === "llvm ir"
      ? Prism.languages.llvm
      : language === "mathematica"
      ? Prism.languages.wolfram
      : language === "objective-c"
      ? Prism.languages.objectivec
      : language === "vb.net"
      ? Prism.languages.vbnet
      : language === "webassembly"
      ? Prism.languages.wasm
      : language === "visual basic"
      ? Prism.languages["visual-basic"]
      : Prism.languages[language] || Prism.languages.plain;

  const highlightedCode = Prism.highlight(codeContent, prismLanguage, language);

  return (
      <figure className="my-1 last:mb-0 group/code-block">
        <div className="relative rounded bg-notion-code_block_background">
          <div className="absolute z-[1] top-2 left-2 flex items-center h-5 px-[0.3125rem] text-notion-caption_color text-[0.75rem] leading-[1.2] opacity-100 md:opacity-0 md:group-hover/code-block:opacity-100 [transition:_opacity_300ms_ease-in]">
            {displayLanguage}
          </div>
          
          <pre className="overflow-x-auto text-left font-notion-code-font text-[85%] [tab-size:_2] pt-[2.125rem] pb-8 pr-4 pl-8 min-h-[1em] text-notion-code_block_color whitespace-pre">
            <code
              data-language={language}
              dangerouslySetInnerHTML={{ __html: highlightedCode }}
            />
          </pre>
        </div>
      </figure>
  );
}
src/components/code.jsx

Step 3: Add PrismJS CSS for Dark and Light Themes

We won’t add the PrismJS CSS directly in the component. Instead, we’ll include it in the global CSS file to support both dark and light themes.

In your global CSS file (e.g., globals.css), add the following CSS for the dark and light themes:

JavaScript
Copy
/* prism css */
.light {
  .token.comment,
  .token.prolog,
  .token.doctype,
  .token.cdata {
    color: slategray;
  }

  .token.punctuation {
    color: #999;
  }

  .token.namespace {
    opacity: 0.7;
  }

  .token.property,
  .token.tag,
  .token.boolean,
  .token.number,
  .token.constant,
  .token.symbol,
  .token.deleted {
    color: #905;
  }

  .token.selector,
  .token.attr-name,
  .token.string,
  .token.char,
  .token.builtin,
  .token.inserted {
    color: #690;
  }

  .token.operator,
  .token.entity,
  .token.url,
  .language-css .token.string,
  .style .token.string {
    color: #9a6e3a;
    /* This background color was intended by the author of this theme. */
    background: hsla(0, 0%, 100%, 0.5);
  }

  .token.atrule,
  .token.attr-value,
  .token.keyword {
    color: #07a;
  }

  .token.function,
  .token.class-name {
    color: #dd4a68;
  }

  .token.regex,
  .token.important,
  .token.variable {
    color: #e90;
  }

  .token.important,
  .token.bold {
    font-weight: bold;
  }
  .token.italic {
    font-style: italic;
  }

  .token.entity {
    cursor: help;
  }
}

.dark {
  .token.comment,
  .token.prolog,
  .token.doctype,
  .token.cdata {
    color: hsl(30, 20%, 50%);
  }

  .token.punctuation {
    opacity: 0.7;
  }

  .token.namespace {
    opacity: 0.7;
  }

  .token.property,
  .token.tag,
  .token.boolean,
  .token.number,
  .token.constant,
  .token.symbol {
    color: hsl(350, 40%, 70%);
  }

  .token.selector,
  .token.attr-name,
  .token.string,
  .token.char,
  .token.builtin,
  .token.inserted {
    color: hsl(75, 70%, 60%);
  }

  .token.operator,
  .token.entity,
  .token.url,
  .language-css .token.string,
  .style .token.string,
  .token.variable {
    color: hsl(40, 90%, 60%);
  }

  .token.atrule,
  .token.attr-value,
  .token.keyword {
    color: hsl(350, 40%, 70%);
  }

  .token.regex,
  .token.important {
    color: #e90;
  }

  .token.important,
  .token.bold {
    font-weight: bold;
  }
  .token.italic {
    font-style: italic;
  }

  .token.entity {
    cursor: help;
  }

  .token.deleted {
    color: red;
  }
}
src/app/global.css

You can find other theme CSS files by navigating to node_modules → prismjs → themes, where you'll find various CSS files. Pick one you like for either dark or light mode, and paste the styles accordingly.

Step 4: How to Use This

Once the setup is complete, you can now use the Code component to display syntax-highlighted code blocks in your Next.js pages.

JavaScript
Copy
import Code from "@/components/code.jsx";

export default function Page() {
  return (
    <main className="bg-white text-black dark:bg-black dark:text-white flex flex-col items-center text-center justify-center w-full min-w-0 min-h-dvh mx-auto max-w-7xl">
      <h1 className="text-4xl font-extrabold mb-2">♥️ Hii</h1>
      <Code
        block={{
          type: "code",
          code: {
            rich_text: [
              {
                type: "text",
                plain_text:
                  "#include <iostream>\n" +
                  "using namespace std;\n" +
                  "\n" +
                  '// A simple C++ program to print "Hello, World!"\n' +
                  "int main() {\n" +
                  '    cout << "Hello, World!" << endl;\n' +
                  "\n" +
                  "    // Variables and basic operations\n" +
                  "    int a = 5, b = 10;\n" +
                  "    int sum = a + b;\n" +
                  "\n" +
                  "    // Print the sum of two numbers\n" +
                  '    cout << "The sum of " << a << " and " << b << " is: " << sum << endl;\n' +
                  "\n" +
                  "    // A basic loop\n" +
                  "    for (int i = 1; i <= 5; i++) {\n" +
                  '        cout << "Iteration: " << i << endl;\n' +
                  "    }\n" +
                  "\n" +
                  "    return 0;\n" +
                  "}",
              },
            ],
            language: "c++",
          },
        }}
      />
      <Code
        block={{
          type: "code",
          code: {
            rich_text: [
              {
                type: "text",
                plain_text:
                  "using System;\n" +
                  "\n" +
                  '// A simple C# program to print "Hello, World!"\n' +
                  "class Program\n" +
                  "{\n" +
                  "    static void Main(string[] args)\n" +
                  "    {\n" +
                  '        Console.WriteLine("Hello, World!");\n' +
                  "    }\n" +
                  "}",
              },
            ],
            language: "c#",
          },
        }}
      />
      <Code
        block={{
          type: "code",
          code: {
            rich_text: [
              {
                type: "text",
                plain_text:
                  "# A simple CoffeeScript program\n" +
                  "square = (x) -> x * x\n" +
                  "\n" +
                  'console.log "The square of 5 is #{square(5)}"',
              },
            ],
            language: "coffeescript",
          },
        }}
      />
      <Code
        block={{
          type: "code",
          code: {
            rich_text: [
              {
                type: "text",
                plain_text:
                  "/* A simple CSS style */\n" +
                  "body {\n" +
                  "    background-color: #f0f0f0;\n" +
                  "    font-family: Arial, sans-serif;\n" +
                  "}\n" +
                  "\n" +
                  "h1 {\n" +
                  "    color: #333;\n" +
                  "    text-align: center;\n" +
                  "}",
              },
            ],
            language: "css",
          },
        }}
      />
    </main>
  );
}

By using this Code component in your page, you will see code blocks with syntax highlighting in both light and dark themes when toggled. Prism will automatically format the code based on its language.

Step 4: Conclusion

Now you have a fully functional static code block syntax highlighting setup in Next.js. The code blocks will be highlighted server-side, and the appropriate syntax themes for both light and dark modes will be applied based on the user's preferences. This solution leverages PrismJS and ensures smooth syntax highlighting for a variety of programming languages.