I migrated my websites into Payload CMS


Sun Jun 14 2026 Β |Β  Jacky Fan Β |Β  5 min read

Overview

I have recently migrate two of my websites into using Payload CMS. One of it is my personal website Jacky.Fan, and the other is my blog website.

Why Payload

Payload is an open-source, code-first headless CMS built with TypeScript and Node.js. Instead of clicking around to define content types, you describe your schema, business logic, and admin UI directly in code, which made it feel much closer to β€œbuilding contents” than β€œconfiguring a CMS.”

Because it is headless, the front end stays completely decoupled. I can keep my site in whatever framework I want, and just pull content over Payload’s REST APIs. On the data side, it runs on infrastructure I control, with MongoDB or PostgreSQL as the database.

One feature I ended up relying on more than I expected was Live Preview. With draft or preview mode turned on, I can see unpublished edits render on the real site instantly while I write, without hitting publish. That feedback loop made migrating and iterating on the new setup noticeably faster.

What changed from the old setup

For my personal website, Jacky.Fan, I had been using Strapi CMS since 2024. However, it was not a good experience. At the time, Strapi CMS did not have Live Preview, so I could not preview content while editing. Also, The integration I implemented was not well‑developed. In the end, I stopped using it to manage CMS content.

For my blog website, I was using the built-in markdown rendering features in Nuxt 3. I wrote blog posts in the Notion app, then converted the content and images into Markdown. I also had to make sure all image URLs were uploaded to my GitHub repo and linked correctly. Overall, it was a complex workflow.

The Coding Part

In this migration, I aimed to simplify my development workflow. As a result, I chose to implement most of the code using GitHub Copilot.

Developing with Github Copilot

At first, I used the new Github Copilot Cloud Agent for the initial coding tasks. However, I noticed that the results were not ideal, and it was difficult to fine-tune them without reading the code. So I switched to VSCode’s Github Copilot Chat for the rest of the development.

Developing with Github Copilot

CMS Scheme

My personal website originally used Strapi CMS, and it already had a well-defined schema. That made it easy for GitHub Copilot to read the schema from the code and adapt it for Payload CMS.

For example, GitHub Copilot brought over collections, fields, and components from Strapi into Payload.

CMS Scheme created by Github Copilot

CMS Components created by Github Copilot

For my blog website, there was no CMS logic in the original code. However, the Markdown post files had well-defined header fields. As a result, GitHub Copilot could still infer the field structure from those files and create the appropriate CMS structure for integration.

Original Markdown Posts Files

CMS Structure created by Github Copilot

Project Structure

Although the two projects use different frameworks (Next.js and Nuxt.js), they share the same overall structure. The framework code sits at the root, and the Payload CMS code lives in the cms folder.

The main reason for this structure is to make it easier for the coding agent to understand the codebase. By keeping both the frontend and CMS code in the same repo, the agent can search across the entire project and build better context, which helps with more complicated tasks.

bash
 ➜  jacky.fan git:(main) tree -L 1.β”œβ”€β”€ Dockerfileβ”œβ”€β”€ README.mdβ”œβ”€β”€ appβ”œβ”€β”€ cmsβ”œβ”€β”€ componentsβ”œβ”€β”€ contextsβ”œβ”€β”€ docker-compose.ymlβ”œβ”€β”€ eslint.config.mjsβ”œβ”€β”€ helpersβ”œβ”€β”€ hooksβ”œβ”€β”€ interfacesβ”œβ”€β”€ next.config.jsβ”œβ”€β”€ node_modulesβ”œβ”€β”€ open-next.config.tsβ”œβ”€β”€ package.jsonβ”œβ”€β”€ pnpm-lock.yamlβ”œβ”€β”€ pnpm-workspace.yamlβ”œβ”€β”€ postcss.config.jsβ”œβ”€β”€ providersβ”œβ”€β”€ publicβ”œβ”€β”€ scriptβ”œβ”€β”€ tailwind.config.tsβ”œβ”€β”€ tsconfig.json└── wrangler.jsoncΒ 12 directories, 13 files 
bash
 ➜  blog.jacky.fan git:(main) βœ— tree -L 1 ..β”œβ”€β”€ Dockerfileβ”œβ”€β”€ README.mdβ”œβ”€β”€ app.vueβ”œβ”€β”€ assetsβ”œβ”€β”€ cmsβ”œβ”€β”€ componentsβ”œβ”€β”€ contentβ”œβ”€β”€ deploy-instruction.mdβ”œβ”€β”€ docker-compose.ymlβ”œβ”€β”€ error.vueβ”œβ”€β”€ node_modulesβ”œβ”€β”€ nuxt.config.tsβ”œβ”€β”€ package.jsonβ”œβ”€β”€ pagesβ”œβ”€β”€ pluginsβ”œβ”€β”€ pnpm-lock.yamlβ”œβ”€β”€ pnpm-workspace.yamlβ”œβ”€β”€ publicβ”œβ”€β”€ serverβ”œβ”€β”€ tailwind.config.tsβ”œβ”€β”€ tsconfig.jsonβ”œβ”€β”€ typesβ”œβ”€β”€ utils└── yarn.lockΒ 12 directories, 13 files 

Server Setup and Deploy

My goal is to deploy these websites to a self-hosted Raspberry Pi 5. This will reduce long-term cloud costs, such as DigitalOcean, and help me learn more about server management.

For deployment, I set everything up with Docker Compose. Docker Compose lets me build and validate locally before deploying to the server, which is a huge advantage. It means I can replicate a production-like environment, iterate faster, and reduce the risk of downtime or performance issues on the live server.

After deploying the websites to my Raspberry Pi 5, I set up Cloudflare Tunnel to expose the applications to the internet and configured my DNS records in Cloudflare. I also set up Access Control from Cloudflare to restrict unauthorized access to sensitive URLs, such as /admin.

In the end, there are a few endpoints:

  • https://jacky.fan β†’ Personal Website
  • https://cms.jacky.fan β†’ CMS for Personal Website
  • https://blog.jacky.fan β†’ Blog Website
  • https://cms-blog.jacky.fan β†’ CMS for Blog Website

Data Migration

For data migration, I wanted to review my content as well, so I chose to migrate manually by copying and pasting it into the CMS. It was also a good chance to review the CMS and website features and spot any bugs so I could fix them immediately.

Wrap-up

  • Migrated two websites β€” Jacky.Fan and blog.jacky.fan β€” from Strapi and Nuxt Markdown to Payload CMS
  • Payload's code-first approach and Live Preview resolved the main pain points: no more blind editing or manual Markdown/image wrangling
  • GitHub Copilot accelerated development, particularly where existing schema (Strapi config or Markdown frontmatter) was available to reference
  • Switched from the Cloud Agent to VSCode Copilot Chat for fine-tuning β€” the monorepo structure helped the agent navigate the full stack more effectively
  • Docker Compose enabled full local testing before touching the live server, reducing risk of downtime
  • Cloudflare Tunnel and Access Control handled public exposure and restricted sensitive URLs (e.g. /admin) without relying on a cloud provider
  • Manual data migration doubled as a QA pass, surfacing bugs early before launch
  • End result: a cleaner, more maintainable setup across both sites with a content editing workflow that is actually pleasant to use