Back to News
Content Management for Personal Sites: Why I Chose Sanity

Content Management for Personal Sites: Why I Chose Sanity

2/22/2026
By Phuong Nha Nguyen6 min read
SanityHeadlessCMSExpress.jsReactJS

Building a full CMS for a personal site is a bit overkill. In this post, I share why Sanity is my go-to Headless CMS, plus my setup using an Express.js proxy to keep API tokens completely secure.

Share:

Right when I started building this website, I thought about writing articles to share my knowledge and personal experiences with everyone. But building a whole custom CMS just for a portfolio felt like "using a sledgehammer to crack a nut"—it takes way too much time and effort.

So, after hanging around ChatGPT for a bit, I found Sanity. It neatly solved my problem: providing a proper place to manage content without making me break my back coding a whole Admin Panel or worrying about database hosting.

So, what exactly is Sanity?

Headless CMS is a game-changer

Fellow devs are probably no strangers to the concept of a Headless CMS. To put it simply, it "chops off" the display part (Frontend) from the data management part (Backend/Admin).

With Sanity, they provide the storage infrastructure (Cloud) and an open-source tool called Sanity Studio for you to build your own input interface. Your job is just to define the data fields, type the content, and hit save. How you fetch and display that data—whether using Next.js, React, or any other framework—is entirely up to you.

My site, nphuonha.id.vn, currently revolves around 3 main content streams: Projects, News, and static pages like Privacy Policy. Using Sanity is more than enough; it completely eliminates the grunt work of setting up a DB and writing bulky CRUD APIs.

The actual workflow

Playing around with Sanity is pretty chill; the basic flow goes like this:

1. Initialize and host the Studio in a flash Just type exactly one command:

bash
1npm create sanity@latest

It generates a folder containing the source code for the Admin page. Just run it on localhost, and you instantly have your data entry dashboard.

And the best part is: You don't have to drag this entire Admin chunk up to Vercel or a VPS to host it! Once you're done coding the config, just type npx sanity deploy. Sanity will automatically throw this Studio onto their servers and hand you back a link (like project-name.sanity.studio). Effortless, and it saves you a deployment slot on Vercel.

2. UI generated from Code (Schema-driven) This is what I love the most. I don't have to go into a UI and drag-and-drop to create database tables. Whatever fields or data types your input form needs, just define them directly using JavaScript/TypeScript.

For example, my project.ts file looks like this:

typescript
1export default {
2  name: 'project',
3  title: 'My Projects',
4  type: 'document',
5  fields: [
6    { name: 'title', title: 'Project Name', type: 'string' },
7    { 
8      name: 'slug', 
9      title: 'URL Slug', 
10      type: 'slug', 
11      options: { source: 'title' } 
12    },
13    { name: 'mainImage', title: 'Cover Image', type: 'image' }
14  ]
15}
16

Save the file, and the Admin UI automatically updates to show a form with text inputs, an auto-generated slug field, and an image upload component. It perfectly fits a developer's mindset.

3. Fetching data and how I "modded" the security flow

Normally, Sanity has a pretty powerful query language called GROQ (Graph-Relational Object Queries). The original scenario is to take the Sanity token and call the API straight from the Frontend to fetch data.

But this is where I had to play my own cards.

Exposing the Sanity token directly on the client side isn't a great security practice (especially when that token has permission to read draft posts). My solution was to sandwich an Express.js server in the middle to act as a proxy.

  • The Sanity token is kept hidden securely in a .env file on the Express.js server.
  • Whenever the Frontend needs data, it calls the internal Express API.
  • Express holds the token, uses GROQ to ping Sanity for the data, and then returns the JSON to the Frontend.

The Express.js code looks roughly like this:

typescript
1app.get('/api/projects', async (req, res) => {
2  try {
3    // Call Sanity from the Backend, keeping the Token safely hidden
4    const projects = await sanityClient.fetch(`*[_type == "project"]{ title, slug, "imageUrl": mainImage.asset->url }`);
5    
6    res.json(projects);
7  } catch (error) {
8    res.status(500).json({ error: 'Failed to fetch data from Sanity' });
9  }
10});

It takes a little extra time to set up the intermediary backend, but in return, I can sleep soundly knowing the data flow is clear and absolutely secure.

Wrapping up

If you guys want to build a blog, portfolio, or personal website but are too lazy to touch traditional databases/backends, Sanity is truly an option worth trying. Quick setup, highly customizable via code, free cloud hosting for the Admin panel, and a generous Free tier. Pairing it with a proxy backend like Express.js is all it takes to have a smooth and secure system.