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.
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.
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.
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.
β 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 β 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 Websitehttps://cms.jacky.fanβ CMS for Personal Websitehttps://blog.jacky.fanβ Blog Websitehttps://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
