🛠️ This template is currently in progress!

I'm building this Notion as a CMS portfolio + blog template in public.

Follow my daily updates on X and Bluesky.

a portfolio website with a blog, built using Next.js with Notion as the CMS

manage all your content (pages, blogs, metadata) directly from your Notion app.

🚀 Launching soon, you'll be able to buy this template and use it for your own portfolio & blog.

❤️ Interested in buying this template? Message me on X or Bluesky.

Text Customization Annotation ⤵️

Bold, Italicize, Underline, Link, code , break, s p a c i ng,

( Keeping semantic tags in mind, the HTML structure is kept minimal — no unnecessary <span> or <div> clutter. Links are rendered using the <a> tag, and code snippets use the <code> tag for better semantic structure. Everything is styled to closely match the look and feel of a Notion page. )

Text Colors & Backgrounds ⤵️

Default, Gray, Brown, Orange, Yellow, Green, Blue, Purple, Pink, Red.

Default, Gray, Brown, Orange, Yellow, Green, Blue, Purple, Pink, Red.

Text Links ⤵️

Color Link

Default, Gray, Brown, Orange, Yellow, Green, Blue, Purple, Pink, Red.

Background Link

Default, Gray, Brown, Orange, Yellow, Green, Blue, Purple, Pink, Red.

External Links:

This link navigates to the Notion website You can add any external website URL from your Notion page. These links open in a new tab using the default <a> tag with target="_blank" and rel="noopener noreferrer" for secure external navigation. It prevents the new tab from being able to control or access your original page, which helps protect your site from certain types of attacks.

Internal Site Links:

This link navigates to the blog page isn’t this great? With this, so many things can open seamlessly! ( These are links to other pages within your site. They use Next.js’s Link component to enable fast client-side navigation and improved performance. When clicked, these links do not open in a new tab. )

Underline customizations⤵️

Default, Gray, Brown, Orange, Yellow, Green, Blue, Purple, Pink, Red.

( you can see here that the underline border inherits text color from the inline code )

Default, Gray, Brown, Orange, Yellow, Green, Blue, Purple, Pink, Red.

Strikethrough customizations⤵️

Default, Gray, Brown, Orange, Yellow, Green, Blue, Purple, Pink, Red.

( you can see here that the underline border inherits text color from the inline code )

Default, Gray, Brown, Orange, Yellow, Green, Blue, Purple, Pink, Red.

Inline Code all customizations ⤵️

Default inline Code

inline code

Color text inline Code

Default, Gray, Brown, Orange, Yellow, Green, Blue, Purple, Pink, Red.

Background inline Code

Default, Gray, Brown, Orange, Yellow, Green, Blue, Purple, Pink, Red.

Different Backgrounds Code side by side

Yellow Green Blue

Default Gray Brown Orange Yellow Green Blue Purple Pink Red.

Different Text Colors inline inline Code side by side

Yellow Green Blue

Default Gray Brown Orange Yellow Green Blue Purple Pink Red

inline code with Bold

Bold

inline code with Italicize

Italicize

inline code with Underline

Default, Gray, Brown, Orange, Yellow, Green, Blue, Purple,Pink, Red

inline code with Strike Through

Default, Gray, Brown, Orange, Yellow, Green, Blue, Purple,Pink, Red

inline code with Equation

E=mc2E = mc²

F=maF = ma

F=G(m1m2)/r2F = G * (m₁ * m₂) / r²

inline code with Link inside

Link

inline code with Emoji inside

const status = "✅ Success";

console.log("🐞 Debug mode enabled");

const emoji = "🧠 + 💻 = 🤖";

mix inline code with all annotations

Notion as a CMS

The Best Part of All ❤️

You can clearly see that these emojis look different, they’re not the default system emojis.

These are Twemojis from X (formerly twitter), styled specifically for the web.

I was able to achieve this by converting the default emojis in the text into Twemojis with this twemoji-parser.

every twemoji supported with skin variation and more

😄 🎉 ❤️ 🔥 🤔 🚀 ✨ 🐱 🧠 🌍 💻 🧡 😎 📚 🙌 🍕 🐶 🖤

People with skin tone variations

"👍🏻", "👍🏼", "👍🏽", "👍🏾", "👍🏿" "🙏🏻", "🙏🏼", "🙏🏽", "🙏🏾", "🙏🏿" "👏🏻", "👏🏼", "👏🏽", "👏🏾", "👏🏿" "👋🏻", "👋🏼", "👋🏽", "👋🏾", "👋🏿"

Gendered professions

"👨‍💻", "👩‍💻", "🧑‍💻" // Developer "👨‍🚀", "👩‍🚀", "🧑‍🚀" // Astronaut "👨‍🎨", "👩‍🎨", "🧑‍🎨" // Artist

Popular and expressive

"🤯", "💀", "🤡", "👀", "🥺", "😭", "🤖", "😵‍💫", "🫶", "🫠", "🫥"

Aesthetic emojis for web UI

"✨", "🌸", "🌈", "🌻", "🪩", "🧃", "🌟", "🌙", "🌀", "💫"

Paragraph Blocs all customizations ⤵️

Plain Text hi there

Color Paragraph you can any color from notion it will exactly replicated

Background Paragraph you can any color from notion it will exactly replicated

blue color

blue background

brown color

brown background

default color

default background

gray color

gray background

green color

green background

orange color

orange background

pink color

pink background

purple color

purple background

red color

red background

yellow color

yellow background

Lorem ipsum dolor sit, Emojis 📢 📝📚🪶📜🌕ִֶָ☾♡💯🚀🎯 👋🏻 using https://github.com/jdecked/twemoji-parser🐦, Bold, Italicize, Underline, My Portfolio link, Code, Break, S p a c i n g. Equations: F=(Gm1m2)/d2F = (G * m1 * m2) / d^2 blue color blue background Link site page referring link 📅 World Emoji Day 📅 World Emoji Day F=(Gm1m2)/d2F = (G * m1 * m2) / d^2 yooo hie lal a alsd v asdh code1code2 yo

_____________

Also note, I have added Dark Mode support you can toggle it by clicking the Sun button in the top right corner.

Every color looks different in Light and Dark Exact replication of Notion Rich Text customization.

____________

Heading 1

Heading 2

Heading 3

Toggle heading 1

Share this project with people who love Notion.

Toggle heading 2

Hi

Toggle heading 3

Share this project with people who love Notion.

Rich Text also supported in all heading 1, 2, 3 and toggle heading 1, 2, 3 blocks also

Heading 1 block with background blue color blue background

Heading 1 block with color blue color blue background

👆🏻You can see above even if a block has a background or color, any text inside it that has its own background or color still shows up just like in Notion. This works thanks to the RichText component.

Heading 2 block with background

Heading 2 block with color

Heading 3 block with background

Heading 3 block with color

_______

Bulleted List Item

Notion colors & backgrounds

  • default background
    • gray background
      • brown background
        • orange background
          • yellow background
            • green background
              • blue background
                • purple background
                  • pink background
                    • red background
  • default color
    • gray color
      • brown color
        • orange color
          • yellow color
            • green color
              • blue color
                • purple color
                  • pink color
                    • red color

  • Boil fresh water
  • Add coffee grounds to your filter or French press
  • Pour hot water over the coffee
  • Let it brew (about 4 minutes for French press)
  • Press or filter the coffee
  • Pour into your favorite mug ☕
  • Add milk or sugar if desired
  • Enjoy your coffee break! 😌

Numbered list Item

  1. 1.
    One
    1. 1.
      Two
      1. 1.
        Three
        1. 1.
          Four
          1. 1.
            Five
            1. 1.
              Six
              1. 1.
                Seven
  2. 2.
    One
    1. 1.
      Two
      1. 1.
        Three
      2. 2.
        Four
        1. 1.
          Five
        2. 2.
          Six
        3. 3.
          Lorem ipsum dolor sit amet
        4. 4.
          Example of rich text with a long paragraph:

Here’s a more complex example with long text inside the numbered list:

  1. 1.
    Main Topic
    1. 1.
      Sub Topic
      1. 1.
        Details
      2. 2.
        Further Explanation
        1. 1.
          Additional information with rich formatting:
          • Bold: Important
          • Italics: Emphasized
        2. 2.
          A long paragraph: Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsa enim dolor illo officia, voluptas facilis fuga ut recusandae quam nulla iure. In, laborum quibusdam!

  1. 1.
    Wake up and stretch
  2. 2.
    Drink a glass of water
  3. 3.
    Make your bed
  4. 4.
    Take a shower
  5. 5.
    Brew some coffee or tea ☕
  6. 6.
    Eat a healthy breakfast
  7. 7.
    Review your to-do list
  8. 8.
    Start your first task

Bulleted List Item and Numbered List Item Rendering

I was able to achieve rendering that exactly matches Notion’s visual and structural style.

  • List item blocks Groups:

    The Notion API provides list items blocks individually, So I group adjacent bulleted_list_item and numbered_list_item blocks into a single block and pass them to their respective components.

  • Semantics:

    Each list is wrapped in a <ul> or <ol>, and each item is rendered using an <li> element, maintaining correct HTML semantics.

  • Bullets:

    Bulleted lists (<ul>) automatically adjust bullet styles based on nesting depth. The style alternates between disc, circle, and square, just like Notion.

  • Numbering:

    Numbered lists (<ol>) automatically adjust numbering based on nesting depth. The numbering style alternates between numbers, letters, and Roman numerals depending on the level, just like Notion.

  • Styling:

    Custom background and text colors for list items are rendered exactly as in Notion. Rich text formatting (bold, italic, links, etc.) is also fully supported inside each list item.

  • Children:

    Nested list items and child blocks are correctly rendered within their parent list item, preserving hierarchy and structure.

Synced Block

this block is synced from blog page

Blog Home Page

Heading 1

Heading 2

Heading 3

Toggle heading 1

Share this project with people who love Notion.

Toggle heading 2

Hi

Toggle heading 3

Share this project with people who love Notion.

Paragraph

  • Bulleted list item
  • one
  • two
  1. 1.
    Numbered list item
  2. 2.
    one
  3. 3.
    two

Toggle block

Toggle block

HI

Empty Toggle block

Background Toggle Block

Hi

Color Toggle Block

Hi

To do Block

To-do item (unchecked)

To-do item (checked)

color to do (checked todo text will not have block color but you can add text color by selecting individual text like this also strike through color can be change like this)

background to do (checked)

unchecked

color to do (unchecked)

background to do (unchecked)

This is a paragraph inside an unchecked to-do.

Toggle inside a to-do

  • Bulleted list item
  • Hi

  1. 1.
    Numbered item
  2. 2.
    Hi

Divider block



Quote block

To be or not to be, that is the question.

— William Shakespeare

Quote Background color

🌕

Simplicity is the ultimate sophistication.

(This quote block has a custom background color.)

Quote Text Color

Stay hungry, stay foolish.

(Text appears in brown in Notion)

Quote with child blocks

Heading 1 inside Quote

Some supporting paragraph text.

  • A bullet list item
  • Another one

Even a nested quote block!

Columns Block

Supports all column layouts: 1, 2, 3, 4, and 5 columns.

This block in Notion is used to organize content side by side, perfect for layouts that need structure and flexibility.

It also accurately reflects custom column widths if you change a column’s width in Notion, it will render with the exact same width here.

Big thanks to the Notion API for providing detailed column width data 🫂

2 Columns

Column 1

Column 2

3 Columns with Custom Widths

Column 1

Column 2

Column 3

4 Columns

Column 1

Column 2

Column 3

Column 4

5 Columns

Column 1

Column 2

Column 3

Column 4

Column 5

🤯 Nested columns? Yes! You can even place a Columns block inside a column — just like Notion allows.

Column 1

Column 1

Column 2

Column 2

Audio Block

Notion-hosted file (uploaded via UI)
Same audio as before, testing if it re-downloads the same audio again.

External already hosted files ( elsewhere (e.g., S3, Dropbox, CDN) )
Externally hosted files (e.g., S3, Dropbox, or any public CDN URLs)

File Block

Figma Keyboard Shortcuts for Windows.pdf

1636 KB
Notion-hosted file (uploaded via UI)

dummy.pdf

Externally hosted files (e.g., S3, Dropbox, or any public CDN URLs)

Handling Notion-hosted and External Files in File & Audio Blocks

In both the File and Audio blocks, we receive either Notion-hosted URLs (files uploaded directly to Notion) or externally hosted URLs (e.g., S3, Dropbox, or public CDN links).

Now, Notion-hosted URLs are temporary, they typically expire after 1 hour. This means if we directly use these URLs in our custom Notion block renderer, the file or audio will stop working shortly after being loaded.

To address this, I implemented a workaround:

  • For Notion-hosted audio block files, we download them during build time and store them in /public/audio/.
  • For Notion-hosted file block files, we store them in /public/file/.
  • For audio files, we extract the exact filename and format from the Notion URL and save it accordingly.
  • For file blocks, we use the filename provided in the Notion API (block.file.name) and preserve it when saving.

I’ve also added a check to avoid redundant downloads, if a file already exists in the respective folder, we reuse it instead of downloading it again.

Although this approach increases the project’s build size, it ensures long-term availability of files without relying on expiring Notion URLs.

I'm also considering uploading these Notion-hosted files to a CDN or cloud storage instead of storing them in the Next.js public folder, to reduce the overall project size.

Code Block

Code block with with notion rich text

JavaScript
export default function Page() {
  return <div>Page</div>;
}
inline code Bold, Italicize, Underline, Link, code ,     break,    s     p   a  c i ng,$F = G * (m₁ * m₂) / r²$Notion as a CMS

JavaScript code block

JavaScript
function debounce(func, delay) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => func.apply(this, args), delay); }; }

// Example usage:
const fetchData = () => { console.log("Fetching data..."); };

const debouncedFetch = debounce(fetchData, 1000);

document.getElementById("search").addEventListener("input", debouncedFetch);
  

TypeScript code block

TypeScript
import React from 'react';

// A simple functional component that returns "Hello, World!"
const HelloWorld: React.FC = () => {
  return (
    <div>
      <h1>Hello, World!</h1>
    </div>
  );
};

// Component with a sum function that calculates the sum of two numbers
const SumComponent: React.FC = () => {
  const sum = (a: number, b: number): number => {
    return a + b;
  };

  return (
    <div>
      <h2>The sum of 5 and 10 is: {sum(5, 10)}</h2>
    </div>
  );
};

// Main App component
const App: React.FC = () => {
  return (
    <div>
      <HelloWorld />
      <SumComponent />
    </div>
  );
};

export default App;

Mermaid

Mermaid
%% A simple Mermaid diagram
graph TD;
    A[Hello, World!] --> B{Sum};
    B -->|5| C[5];
    B -->|10| D[10];
    C --> E[Result];
    D --> E;
Mermaid
graph TD;
  A[User] -->|Enters URL| B[Browser]
  B -->|Sends HTTP Request| C[DNS Resolver]
  C -->|Resolves Domain to IP| D[Web Server]
  D -->|Processes Request| E{Static or Dynamic Content?}
  
  E -->|Static| F[Serve Static Files]
  E -->|Dynamic| G[Application Server]
  
  G -->|Fetches Data| H[Database]
  H -->|Returns Data| G
  G -->|Generates HTML| I[Web Server]
  
  F -->|Sends Response| J[Browser]
  I -->|Sends Response| J
  
  J -->|Parses HTML| K[DOM Construction]
  K -->|Loads Resources| L[CSS, JS, Images]
  L -->|Executes JS| M[Render Page]
  M -->|Displays Content| N[User Sees Website]
Mermaid
flowchart LR
    id1>This is the text in the box]
Mermaid
erDiagram
          CUSTOMER }|..|{ DELIVERY-ADDRESS : has
          CUSTOMER ||--o{ ORDER : places
          CUSTOMER ||--o{ INVOICE : "liable for"
          DELIVERY-ADDRESS ||--o{ ORDER : receives
          INVOICE ||--|{ ORDER : covers
          ORDER ||--|{ ORDER-ITEM : includes
          PRODUCT-CATEGORY ||--|{ PRODUCT : contains
          PRODUCT ||--o{ ORDER-ITEM : "ordered in"
Mermaid
flowchart TD
    A[Start] --> B[Process]
    B --> C{Decision}
    C -->|Yes| D[Result 1]
    C -->|No| E[Result 2]
Mermaid
flowchart TD
Mermaid:::frenzy-->NonBreakableSpace-->ReportTo --> NotionHQ & MermaidOnGithub
MermaidOnGithub-->GitHub:::frenzy
Mermaid-->Mermaids_FlowchartsArticle:::linked_frenzy-->SpecialChars:::linked
Diagrams.net--> GitHub
Mermaid-->DiagramTypes-->ZILLIONSOFTANGENTS["ZILLIONS OF TANGENTS #129325;"]:::frenzy-->ZILLIONSOFTANGENTS
Mermaids_FlowchartsArticle-->Share-->Tw_Notion & Tw_Nerd

Mermaid-->Yt_Nutt_FlowchartGenerator["William Nutt's<br/>Flowchart Generator<br/>."]:::linked

Mermaids_FlowchartsArticle-->WebSrch_YedToMermaid
MiscTangents:::frenzy--> GitHub & CSS & AS-ADHD & Datamining
WebSrch_YedToMermaid:::frenzy-->Diagrams.net:::frenzy
GitHub-->GitLab & MediaProduction_InGit
MediaProduction_InGit-->MediaProduction_Tools-->MediaProduction_InCloud & Linux & VFX & AWS & Deep & OBS
VFX-->Blender & AfterEffects_Alternatives