Back
Amine Ben Yedder
Amine Ben Yedder
Step-by-Step Sanity Guide: Adding Code Blocks to Your Sanity + Next.js Blog

Step-by-Step Sanity Guide: Adding Code Blocks to Your Sanity + Next.js Blog

Part 1: Step-by-Step Sanity Guide: Setting Up Sanity.io with Next.js

Part 2: Step-by-Step Sanity Guide: Adding Code Blocks to Your Sanity + Next.js Blog

Introduction

Code blocks are essential for any technical blog. They allow you to share code snippets, configuration files, and terminal commands in a clean, readable format. In this guide, we'll walk through implementing beautiful, syntax-highlighted code blocks in your Sanity + Next.js blog.

What You'll Build

By the end of this tutorial, you'll have:

  • A custom code block schema in Sanity Studio
  • Syntax-highlighted code blocks on your frontend
  • Copy-to-clipboard functionality
  • Language-specific styling

Prerequisites

Before we start, make sure you have:

Step 1: Create the Code Block Schema in Sanity

First, let's define a custom schema for code blocks in your Sanity Studio.

Create a new file src/sanity/schemaTypes/codeBlockType.tsx

src/sanity/schemaTypes/codeBlockType.tsx
1import { CodeIcon } from "@sanity/icons"; 2import { defineType } from "sanity"; 3 4export const codeBlockType = defineType({ 5 name: "codeBlock", 6 type: "object", 7 title: "Code Block", 8 icon: CodeIcon, 9 fields: [ 10 { 11 name: "language", 12 type: "string", 13 title: "Language", 14 options: { 15 list: [ 16 { title: "JavaScript", value: "javascript" }, 17 { title: "TypeScript", value: "typescript" }, 18 { title: "HTML", value: "html" }, 19 { title: "CSS", value: "css" }, 20 { title: "Python", value: "python" }, 21 { title: "Java", value: "java" }, 22 { title: "PHP", value: "php" }, 23 { title: "Ruby", value: "ruby" }, 24 { title: "C#", value: "csharp" }, 25 { title: "C++", value: "cpp" }, 26 { title: "Shell", value: "shell" }, 27 { title: "SQL", value: "sql" }, 28 { title: "JSON", value: "json" }, 29 { title: "Plain Text", value: "text" }, 30 ], 31 }, 32 initialValue: "javascript", 33 }, 34 { 35 name: "filename", 36 type: "string", 37 title: "File Name", 38 description: "Optional file name to display", 39 }, 40 { 41 name: "code", 42 type: "text", 43 title: "Code", 44 rows: 10, 45 }, 46 ], 47 preview: { 48 select: { 49 language: "language", 50 filename: "filename", 51 code: "code", 52 }, 53 prepare({ language, filename, code }) { 54 const title = filename 55 ? `${filename} (${language})` 56 : `Code block (${language})`; 57 const firstLine = code?.split("\n")[0]; 58 return { 59 title: title, 60 subtitle: firstLine, 61 }; 62 }, 63 }, 64}); 65

Step 2: Update Your Blog Post Schema

Next, add the code block to your blog post schema. Update your src/sanity/schemaTypes/blockContentType.ts file:

src/sanity/schemaTypes/blockContentType.ts
1import { defineType, defineArrayMember } from "sanity"; 2import { ImageIcon } from "@sanity/icons"; 3 4/** 5 * This is the schema type for block content used in the post document type 6 * Importing this type into the studio configuration's `schema` property 7 * lets you reuse it in other document types with: 8 * { 9 * name: 'someName', 10 * title: 'Some title', 11 * type: 'blockContent' 12 * } 13 */ 14 15export const blockContentType = defineType({ 16 title: "Block Content", 17 name: "blockContent", 18 type: "array", 19 of: [ 20 defineArrayMember({ 21 type: "block", 22 // Styles let you define what blocks can be marked up as. The default 23 // set corresponds with HTML tags, but you can set any title or value 24 // you want, and decide how you want to deal with it where you want to 25 // use your content. 26 styles: [ 27 { title: "Normal", value: "normal" }, 28 { title: "H1", value: "h1" }, 29 { title: "H2", value: "h2" }, 30 { title: "H3", value: "h3" }, 31 { title: "H4", value: "h4" }, 32 { title: "Quote", value: "blockquote" }, 33 ], 34 lists: [{ title: "Bullet", value: "bullet" }], 35 // Marks let you mark up inline text in the Portable Text Editor 36 marks: { 37 // Decorators usually describe a single property – e.g. a typographic 38 // preference or highlighting 39 decorators: [ 40 { title: "Strong", value: "strong" }, 41 { title: "Emphasis", value: "em" }, 42 ], 43 // Annotations can be any object structure – e.g. a link or a footnote. 44 annotations: [ 45 { 46 title: "URL", 47 name: "link", 48 type: "object", 49 fields: [ 50 { 51 title: "URL", 52 name: "href", 53 type: "url", 54 }, 55 ], 56 }, 57 ], 58 }, 59 }), 60 // You can add additional types here. Note that you can't use 61 // primitive types such as 'string' and 'number' in the same array 62 // as a block type. 63 defineArrayMember({ 64 type: "image", 65 icon: ImageIcon, 66 options: { hotspot: true }, 67 fields: [ 68 { 69 name: "alt", 70 type: "string", 71 title: "Alternative Text", 72 }, 73 ], 74 }), 75 defineArrayMember({ 76 type: "codeBlock", 77 }), 78 ], 79}); 80

Step 3: Register the Schema

Don't forget to register your new schema in the src/sanity/schemaTypes/index.ts file:

src/sanity/schemaTypes/index.ts
1import { type SchemaTypeDefinition } from "sanity"; 2 3import { blockContentType } from "./blockContentType"; 4import { categoryType } from "./categoryType"; 5import { postType } from "./postType"; 6import { authorType } from "./authorType"; 7import { codeBlockType } from "./codeBlockType"; 8 9export const schema: { types: SchemaTypeDefinition[] } = { 10 types: [blockContentType, categoryType, postType, authorType, codeBlockType], 11};

Step 4: Add Code Block to Your Blog Body

Add a code block in Sanity Studio:

  1. Open your Studio at http://localhost:3000/studio
  2. Select a post
  3. In the body section, click the Code block button (</>)
  4. Fill in the required inputs (language, code, and file name)
Create code block in blog body preview

Step 5: Install Frontend Dependencies

Now let's set up the frontend. Install the necessary packages:

terminal
1npm i react-syntax-highlighter @types/react-syntax-highlighter

Step 6: Define the CodeBlock Type:

Start by adding a new type by creating the src/types/codeBlock.ts file

src/types/codeBlock.ts
1export type CodeBlockType = { 2 language: string; 3 code: string; 4 filename: string; 5}; 6

Step 7: Implement the CodeBlock Component:

Create a new component src/components/CodeBlock.tsx

codeBlock.tsx
1"use client"; 2import { useState } from "react"; 3import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; 4import { vscDarkPlus } from "react-syntax-highlighter/dist/cjs/styles/prism"; 5import { ClipboardIcon, CheckmarkIcon } from "@sanity/icons"; 6import { CodeBlockType } from "@/types/codeBlock"; 7 8type CodeBlockProps = CodeBlockType; 9export default function CodeBlock({ 10 language = "text", 11 code, 12 filename, 13}: CodeBlockProps) { 14 const [copied, setCopied] = useState(false); 15 16 const copyToClipboard = async () => { 17 try { 18 await navigator.clipboard.writeText(code); 19 setCopied(true); 20 setTimeout(() => setCopied(false), 2000); 21 } catch (err) { 22 console.error("Failed to copy code: ", err); 23 } 24 }; 25 26 return ( 27 <div className="my-6 overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700"> 28 {/* Header */} 29 <div className="flex items-center justify-between bg-gray-50 dark:bg-gray-800 px-4 py-2"> 30 <div className="flex items-center space-x-2"> 31 {filename && ( 32 <span className="text-sm text-gray-600 dark:text-gray-300 font-mono"> 33 {filename} 34 </span> 35 )} 36 </div> 37 38 <button 39 onClick={copyToClipboard} 40 className="flex items-center space-x-1 text-sm text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-100 transition-colors" 41 title="Copy to clipboard" 42 > 43 {copied ? ( 44 <CheckmarkIcon className="h-4 w-4 text-green-500" /> 45 ) : ( 46 <ClipboardIcon className="h-4 w-4" /> 47 )} 48 <span>{copied ? "Copied!" : "Copy"}</span> 49 </button> 50 </div> 51 52 {/* Code */} 53 <div className="relative"> 54 <SyntaxHighlighter 55 language={language} 56 style={vscDarkPlus} 57 customStyle={{ 58 margin: 0, 59 borderRadius: 0, 60 fontSize: "14px", 61 lineHeight: "1.5", 62 }} 63 wrapLines 64 showLineNumbers 65 lineNumberStyle={{ 66 minWidth: "3em", 67 paddingRight: "1em", 68 color: "#6b7280", 69 borderRight: "1px solid #374151", 70 marginRight: "1em", 71 }} 72 > 73 {code} 74 </SyntaxHighlighter> 75 </div> 76 </div> 77 ); 78} 79

Step 8: Update Your Portable Text Renderer:

Update the ProtableText component props in the src/components/blog.tsx file

src/components/blog.tsx
1import { PostType } from "@/types/post"; 2import { PortableText } from "next-sanity"; 3import Image from "next/image"; 4import React from "react"; 5import CodeBlock from "./codeBlock"; 6import { CodeBlockType } from "@/types/codeBlock"; 7 8type CodeBlockProps = { 9 value: CodeBlockType; 10}; 11 12type BlogProps = { 13 post: PostType; 14}; 15 16export const Blog = ({ 17 post: { 18 title, 19 author, 20 mainImage: { url, alt }, 21 body, 22 }, 23}: BlogProps) => { 24 return ( 25 <article className="flex flex-col gap-4"> 26 <h1 className="text-center text-4xl font-bold text-yellow-300"> 27 {title} 28 </h1> 29 <span className="self-end capitalize font-semibold"> 30 made by: <span className="text-orange-300">{author}</span> 31 </span> 32 <div className="relative aspect-[3/1.75]"> 33 <Image 34 className="object-cover rounded-3xl" 35 src={url} 36 alt={alt} 37 priority 38 fill 39 /> 40 </div> 41 <PortableText 42 value={body} 43 components={{ 44 block: { 45 normal: ({ children }) => ( 46 <p className="leading-7 sm:text-lg">{children}</p> 47 ), 48 h3: ({ children }) => ( 49 <h2 className="text-pink-300 font-semibold tracking-tight text-xl sm:text-3xl"> 50 {children} 51 </h2> 52 ), 53 }, 54 list: { 55 bullet: ({ children }) => ( 56 <ul className="sm:text-lg list-disc ml-6">{children}</ul> 57 ), 58 }, 59 listItem: { 60 bullet: ({ children }) => ( 61 <li className="marker:font-bold">{children}</li> 62 ), 63 }, 64 types: { 65 codeBlock: ({ 66 value: { language, code, filename }, 67 }: CodeBlockProps) => ( 68 <CodeBlock language={language} code={code} filename={filename} /> 69 ), 70 }, 71 }} 72 /> 73 </article> 74 ); 75}; 76

Step 9: Observe and Test the Results:

Run your Next.js project and check if the code block is rendered successfully

Code block in frontend preview

Make sure the copy to clipboard functionality works

Conclusion

You now have a fully functional code block system in your Sanity + Next.js blog! Your readers can enjoy syntax-highlighted code with copy functionality, and you can easily manage code snippets through Sanity Studio.

What You've Built

  • Advanced Sanity schema for code blocks with line numbering and Premium syntax highlighting.
  • Smart copy-to-clipboard with format preservation and success notifications.

The tutorial is designed to be beginner-friendly while including advanced features that make your code blocks professional and user-friendly. Each step includes complete code examples and explanations of what's happening.

Happy coding! 🚀

Part 1: Step-by-Step Sanity Guide: Setting Up Sanity.io with Next.js

Part 2: Step-by-Step Sanity Guide: Adding Code Blocks to Your Sanity + Next.js Blog