tudy.club
BlogResourcesAbout
KO

© 2024 tudy.club

← Back to Blog
Building a Notion-Powered Blog with Claude Code
Ddev
Nnotion
Aai

Building a Notion-Powered Blog with Claude Code

How I built this blog using Notion as a CMS with Next.js and Claude Code, after years of trying different platforms from WordPress to Sanity.

2026-02-02•8 min read
#Claude#Claude Code

Building a Notion-Powered Blog with Claude Code

2026-02-02

The blog you're looking at right now is built on a Notion database! I bought the tudy.club domain and let it sit for like... n years. I always wanted a place to document the things I like, things I've learned, and things worth recording. So I spent a long time looking for something that would let me shape and display my data exactly the way I wanted.


Why I Built My Own Blog

Why Not Naver Blog or Tistory?

I tried both, and I'm still casually running AdPost on my Naver blog. It's convenient, for sure. But there were things that bugged me.

Naver Blog is great for showing up in Naver and Google search results, but that's also its downside. Your data is locked into the Naver ecosystem. I wanted to cover dev-related content too, and I needed more flexibility with code blocks and data visualization. The embed limitations and photo uploads were annoying. The Naver editor just wasn't cutting it.

Tistory was the same story. You're still on someone else's platform, and it never really felt like I owned my data. Velog improved a lot in that regard, but what I wanted most was comfortable control over my own data.

Honestly, this desire to own my data goes way back. I set up something similar with WordPress back in high school. But I just... stopped visiting it. Set it up and abandoned it.

That pattern kept repeating. Build it, don't use it, abandon it. To break this loop, the actual writing process had to be as frictionless as possible.

What About Webflow or Framer?

I've been using Webflow and Framer well since 2022, and I even set up previous companies' websites with them, so I originally planned to go that route. But paying monthly felt wasteful. I've been dabbling in coding since 2020, and I already knew how to build things. Why pay extra just for a custom domain and manually upload data? Using a no-code builder felt unnecessary.

Lessons from the Sanity Era

So the first thing I tried for building it myself was Sanity. In January 2023, I built a portfolio site using Sanity as a CMS. Sanity is a headless CMS that feels similar to Notion. The flexibility is genuinely great. But the problem was that designing the data schema and wiring everything up at the beginning was really, really painful.

  1. Define what data I want to put in
  2. Style how that data should be displayed

Back in 2023, AI wasn't as capable as it is now, so I had to do all of that manually lol.

I managed to build it somehow, but looking back, I spent more time building the website than focusing on the actual portfolio. My TypeScript skills improved a lot, I'll give it that... but that's not really the point of a blog, right? lol


So, Notion It Is

After going around in circles, I settled on Notion. I'd been using Notion daily since 2020. Writing there, managing projects there. So why not write blog posts in Notion too and have them automatically show up on the site?

Write in Notion, change the status to Published, redeploy, done. Data processing is flexible, and I can naturally use code blocks and tables that dev content needs right inside Notion.

Starting Point: morethan-log

I didn't build everything from scratch. There's an open-source Notion blog template called morethan-log. It's a Next.js static blog that uses a Notion database as a CMS. Clean Vercel deployment support too.

I borrowed a similar Notion DB structure from there, but customized the design and features to match what I wanted.

Tech Stack

  • Next.js + TypeScript: Went with TypeScript since I was already comfortable with it (thanks to the Sanity days...)
  • Tailwind CSS: I used to use Styled Components and SASS, but Tailwind felt like it covered both, so I switched.
  • shadcn/ui: I just wanted to use it. Each component is beautiful and easy to customize.
  • Notion API: Serves as the CMS
  • Vercel: Pretty much all my deployments go through Vercel

1. Creating a Notion Integration

Head to notion.so/my-integrations and create a new Integration. Give it a name, and you'll get an API token in the format secret_xxxxx. Put this in your NOTION_TOKEN environment variable.

2. Connecting the Database

Create a Notion database to manage your blog posts, then go to the page's top-right menu (···) > Connections > add the Integration you just created. If you skip this step, the API won't be able to access the database.

Extract the ID from the database URL and put it in the NOTION_DATABASE_ID environment variable.

https://notion.so/myworkspace/a1b2c3d4e5f6...?v=...
                            ^^^^^^^^^^^^^^^^
                            This part is the DATABASE_ID

3. Database Schema

Here are the properties I use:

PropertyTypePurpose
titleTitlePost title
slugTextURL path (e.g., my-first-post)
statusSelectPublic / Draft
typeSelectPost / Resource
categoryMulti-selectAI, Dev, Design, etc.
dateDatePublish date
tagsMulti-selectTags
summaryTextPreview description (auto-extracted from body if empty)
thumbnailFilesCover image

Only items with status Public get fetched by the API, so just switching the status in Notion toggles publish/unpublish.

4. Connecting in Code

import { Client } from "@notionhq/client";

const notion = new Client({ auth: process.env.NOTION_TOKEN });

const response = await notion.databases.query({
  database_id: process.env.NOTION_DATABASE_ID,
  filter: {
    property: "status",
    select: { equals: "Public" },
  },
  sorts: [{ property: "date", direction: "descending" }],
});

@notionhq/client is the official SDK, and notion-to-md converts block data to markdown. Notion's block structure is pretty complex, but this library handles most of it.

5. Publishing Flow

  1. Write a post in Notion
  2. Change status to Public
  3. Redeploy on Vercel (or automate with a webhook)

That's it. Since Notion serves as the CMS, there's no need for a separate admin page.


Tudy Club

Tudy Club is a name derived from (s)tudy club. I designed it so the "s" overlap could be used like products.tudy.club.

Blog Concept: Subway Map

The concept behind tudy.club is a subway map. Categories are split into AI, Crypto, Design, Dev, Money, Notion, and Product, and I thought of them as subway lines. Each category is a line, and each post is a station.

When you open a post detail page, you'll see a ticket-style header. Like the ticket you'd get when arriving at a station.

Just like subway lines intersect, learning expands across categories too. You're studying AI and suddenly you're in Dev territory. You dig into Product and it overlaps with Design. Transfer stations where multiple lines meet start to emerge. I imagined it growing and branching out like that.

The purpose of this blog is to log what I learn as I go and spread it out from there. That's why I gave it the subtitle "Growth log of a Gen-Z PM who wants to live better." It's not about delivering finished knowledge -- it's about documenting the process of learning.

What Claude Code Did

I built this blog with help from Claude Code. The job was to reference morethan-log's DB structure while overhauling the design and features to match my concept.

  1. Initial setup and structuring

    I started from morethan-log but handed the task of transforming it into my desired structure to Claude Code. From swapping the styling stack to Tailwind + shadcn/ui to organizing the routing structure.

    With Sanity, this kind of work took weeks. With Claude Code, it was done in hours.

  2. Notion block rendering

    The block data from the Notion API comes in JSON, so you have to build your own renderer. Creating components for each block type -- text, headings, lists, code blocks, images, callouts, etc. -- Claude Code is genuinely fast at this kind of repetitive implementation.


Things I Struggled With

1. Notion API Image Expiration

The most annoying problem when building a blog with the Notion API is image expiration. Image URLs hosted by Notion have query parameters like X-Amz-Expires, and the links die after about an hour.

At first I considered shortening the ISR revalidate interval, but I decided caching images locally at build time was cleaner.

// Hash the URL excluding expiration query parameters
const urlObj = new URL(url);
const baseUrl = `${urlObj.origin}${urlObj.pathname}`;
return crypto.createHash('md5').update(baseUrl).digest('hex');

The key is hashing the base URL without query parameters instead of the full URL. Even if the same image comes in with a different expiration time, you get the same hash, so if it's already cached, there's no need to re-download.

At build time, images get saved to public/cached-images/, and the frontend accesses them at /cached-images/{hash}.jpg. When deployed to Vercel, these images land on the CDN with no more expiration worries.

2. Bookmark Card OG Data Auto-Fetch

When you add a URL as a bookmark in Notion, it shows up as a nice card. But through the API, you just get the plain URL text. No OG metadata (title, description, thumbnail).

So I built a custom transformer for the notion-to-md library. When it encounters a bookmark block, it fetches the URL's OG data via the Microlink API and renders it as a rich card.

n2m.setCustomTransformer("bookmark", async (block: any) => {
  const url = block.bookmark?.url || "";
  
  // Fetch OG metadata via Microlink
  const res = await fetch(`https://api.microlink.io?url=${encodeURIComponent(url)}`);
  const data = await res.json();
  
  // Cache thumbnail to prevent expiration
  const cachedImage = data.data.image?.url 
    ? await cacheImage(data.data.image.url) 
    : '';
    
  return `🔗BOOKMARK${JSON.stringify({ url, title, description, image, domain })}`;
});

The Microlink API free tier is more than enough, and the fetched thumbnail images are saved by reusing the caching system I built above. Just paste a link in Notion and it shows up as a card on the blog.

3. Cost Comparison

Custom domain on Framer costs $5/month ($60/year). Webflow is $14/month ($168/year). Both go up even more if you use CMS features.

My current stack:

  • Vercel: Free (Hobby plan)
  • Notion: Already using it, so zero additional cost
  • Domain: ~$12/year (same anywhere)

Annual savings: $48 to $156

Honestly, it wasn't really about saving money. It was more like: I already know how to build things, so why lock myself into a no-code builder? Claude Code also significantly lowered the barrier to building it myself.


The Result

https://tudy.club/
tudy.club

Here's how the writing process changed:

  • Before: Fighting with Sanity schemas / Abandoning WordPress
  • After: Write in Notion, change Status to Published, done

What I Learned Along the Way

Naver Blog locks in your data, Tistory is the same deal, Framer feels like a waste of money, WordPress was set up in high school and abandoned, Sanity only improved my TypeScript skills. After going around in circles, the Notion + Next.js + Claude Code combo turned out to be the right answer for me.

The key is minimizing the barrier to writing. No matter how cool the setup is, it's meaningless if you don't actually write. Using Notion -- a tool I already use every day -- to write posts and having everything else run automatically was the best decision I made this time.

If you've been sitting on a domain you bought and never used, or if you've been bouncing between platforms like I did, I'd recommend this approach. There are great starting points like morethan-log, and with Claude Code, you can build it in a single weekend day.

목차

  • Why I Built My Own Blog
  • Why Not Naver Blog or Tistory?
  • What About Webflow or Framer?
  • Lessons from the Sanity Era
  • So, Notion It Is
  • Starting Point: morethan-log
  • Tech Stack
  • 1. Creating a Notion Integration
  • 2. Connecting the Database
  • 3. Database Schema
  • 4. Connecting in Code
  • 5. Publishing Flow
  • Tudy Club
  • Blog Concept: Subway Map
  • What Claude Code Did
  • Things I Struggled With
  • 1. Notion API Image Expiration
  • 2. Bookmark Card OG Data Auto-Fetch
  • 3. Cost Comparison
  • The Result
  • What I Learned Along the Way