Skip to main content

Overview

This guide covers integrating Code Syntactic Sugar with Next.js applications, including both the App Router (Next.js 13+) and Pages Router architectures. We’ll explore client and server component patterns, optimal CSS loading strategies, and production-ready implementations.

Installation

First, install the required dependencies:
npm install code-syntactic-sugar
Code Syntactic Sugar requires React 18 or 19 as a peer dependency. Next.js 13+ already includes compatible React versions.

App Router Implementation

The App Router uses React Server Components by default, but Code Syntactic Sugar needs to run on the client side since it generates React elements.
Create a client component for code highlighting:
app/components/CodeBlock.tsx
'use client';

import { highlight, type CodeSyntacticSugarConfig } from 'code-syntactic-sugar';
import { useState } from 'react';
import './CodeBlock.css';

interface CodeBlockProps {
  code: string;
  language?: string;
  showLineNumbers?: boolean;
  fileName?: string;
  highlightedLines?: number[];
  addedLines?: number[];
  removedLines?: number[];
}

export function CodeBlock({
  code,
  language = 'javascript',
  showLineNumbers = false,
  fileName,
  highlightedLines = [],
  addedLines = [],
  removedLines = [],
}: CodeBlockProps) {
  const [copied, setCopied] = useState(false);

  const config: CodeSyntacticSugarConfig = {
    modifiers: {
      highlightedLines: highlightedLines.length > 0 
        ? (highlightedLines as [number, ...number[]]) 
        : undefined,
      addedLines: addedLines.length > 0 
        ? (addedLines as [number, ...number[]]) 
        : undefined,
      removedLines: removedLines.length > 0 
        ? (removedLines as [number, ...number[]]) 
        : undefined,
    },
  };

  const highlightedCode = highlight(code, config);

  const handleCopy = async () => {
    await navigator.clipboard.writeText(code);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  return (
    <div className="code-block">
      <div className="code-block-header">
        <div className="code-block-info">
          {fileName ? (
            <span className="file-name">{fileName}</span>
          ) : (
            <span className="language-label">{language}</span>
          )}
        </div>
        <button 
          className="copy-button" 
          onClick={handleCopy}
          aria-label="Copy code to clipboard"
        >
          {copied ? 'Copied!' : 'Copy'}
        </button>
      </div>
      <pre className={showLineNumbers ? 'with-line-numbers' : ''}>
        <code>{highlightedCode}</code>
      </pre>
    </div>
  );
}

Pages Router Implementation

Create a reusable component in the Pages Router:
components/CodeBlock.tsx
import { highlight, type CodeSyntacticSugarConfig } from 'code-syntactic-sugar';
import { useState } from 'react';
import styles from './CodeBlock.module.css';

interface CodeBlockProps {
  code: string;
  language?: string;
  showLineNumbers?: boolean;
  fileName?: string;
  highlightedLines?: number[];
}

export function CodeBlock({
  code,
  language = 'javascript',
  showLineNumbers = false,
  fileName,
  highlightedLines = [],
}: CodeBlockProps) {
  const [copied, setCopied] = useState(false);

  const config: CodeSyntacticSugarConfig = highlightedLines.length > 0 ? {
    modifiers: {
      highlightedLines: highlightedLines as [number, ...number[]],
    },
  } : undefined;

  const highlightedCode = highlight(code, config);

  const handleCopy = async () => {
    await navigator.clipboard.writeText(code);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  return (
    <div className={styles.codeBlock}>
      <div className={styles.header}>
        <div className={styles.info}>
          {fileName ? (
            <span className={styles.fileName}>{fileName}</span>
          ) : (
            <span className={styles.language}>{language}</span>
          )}
        </div>
        <button 
          className={styles.copyButton} 
          onClick={handleCopy}
        >
          {copied ? 'Copied!' : 'Copy'}
        </button>
      </div>
      <pre className={showLineNumbers ? styles.withLineNumbers : undefined}>
        <code>{highlightedCode}</code>
      </pre>
    </div>
  );
}

MDX Integration

For content-heavy sites using MDX with Next.js:
'use client';

import { highlight } from 'code-syntactic-sugar';
import type { ReactElement } from 'react';

interface MDXCodeBlockProps {
  children: string;
  className?: string;
  showLineNumbers?: boolean;
}

export function MDXCodeBlock({ 
  children, 
  className,
  showLineNumbers = false 
}: MDXCodeBlockProps): ReactElement {
  // Extract language from className (format: language-js)
  const language = className?.replace(/language-/, '') || 'text';
  
  const highlightedCode = highlight(children.trim());

  return (
    <div className="mdx-code-block">
      <div className="code-header">
        <span className="language-badge">{language}</span>
      </div>
      <pre className={showLineNumbers ? 'with-line-numbers' : ''}>
        <code>{highlightedCode}</code>
      </pre>
    </div>
  );
}
When using MDX, the code component receives the code content as children and the language as part of the className prop (e.g., language-javascript).

API Routes Example

Show code examples in API documentation:
app/api/docs/page.tsx
'use client';

import { CodeBlock } from '@/components/CodeBlock';

export default function APIDocsPage() {
  const fetchExample = `
// Fetch user data
const response = await fetch('/api/users/123', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_TOKEN',
    'Content-Type': 'application/json',
  },
});

const user = await response.json();
console.log(user);
  `.trim();

  const postExample = `
// Create a new user
const response = await fetch('/api/users', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'John Doe',
    email: '[email protected]',
  }),
});

const newUser = await response.json();
  `.trim();

  return (
    <div className="api-docs">
      <h1>API Documentation</h1>
      
      <section>
        <h2>GET /api/users/:id</h2>
        <p>Retrieve a user by ID</p>
        <CodeBlock 
          code={fetchExample}
          language="javascript"
          showLineNumbers={true}
          highlightedLines={[2, 3, 4, 5, 6]}
        />
      </section>

      <section>
        <h2>POST /api/users</h2>
        <p>Create a new user</p>
        <CodeBlock 
          code={postExample}
          language="javascript"
          showLineNumbers={true}
          highlightedLines={[2, 3, 4, 5, 6, 7, 8, 9, 10]}
        />
      </section>
    </div>
  );
}

Performance Optimization

Dynamic Imports

For better code splitting, dynamically import the CodeBlock component:
import dynamic from 'next/dynamic';

const CodeBlock = dynamic(() => 
  import('./components/CodeBlock').then(mod => ({ default: mod.CodeBlock }))
, {
  loading: () => <div className="code-block-skeleton">Loading...</div>,
  ssr: false,
});

Memoization

Cache highlighted code to avoid re-processing:
'use client';

import { highlight } from 'code-syntactic-sugar';
import { useMemo } from 'react';

export function CodeBlock({ code, highlightedLines }) {
  const highlightedCode = useMemo(
    () => highlight(code, { 
      modifiers: { highlightedLines } 
    }),
    [code, highlightedLines]
  );

  return <pre><code>{highlightedCode}</code></pre>;
}
Always mark components that use Code Syntactic Sugar with 'use client' directive in Next.js App Router, as the library generates React elements at runtime.

Common Patterns

Loading Code from Files

app/examples/[slug]/page.tsx
import { promises as fs } from 'fs';
import path from 'path';
import { CodeBlock } from '@/components/CodeBlock';

interface PageProps {
  params: { slug: string };
}

export default async function ExamplePage({ params }: PageProps) {
  const filePath = path.join(process.cwd(), 'examples', `${params.slug}.js`);
  const code = await fs.readFile(filePath, 'utf-8');

  return (
    <div>
      <h1>Example: {params.slug}</h1>
      <CodeBlock 
        code={code}
        fileName={`${params.slug}.js`}
        showLineNumbers={true}
      />
    </div>
  );
}

Diff View for Documentation

function DiffExample() {
  const code = `
function oldFunction(x) {
  return x * 2;
}

function newFunction(x, multiplier = 2) {
  return x * multiplier;
}

const result = newFunction(5, 3);
  `.trim();

  return (
    <CodeBlock
      code={code}
      removedLines={[1, 2, 3]}
      addedLines={[5, 6, 7]}
      showLineNumbers={true}
      fileName="refactor.js"
    />
  );
}

Build docs developers (and LLMs) love