3 min read

How I Built My Personal Website Using Nextjs with Payload

Mehdi Rezaei
Mehdi
Author

I rebuilt mehd.ir on Next.js and Payload CMS so the site could be both a portfolio and a real publishing system. Static markdown was fine for a few pages. It fell over once I wanted drafts, scheduled posts, media, SEO fields, and an admin UI I would actually use.

This is the stack, the content model choices, and the sharp edges I hit shipping it.

Why Next.js plus Payload

Next.js covers the public site: App Router, React Server Components, image optimization, and deployment on Vercel without a separate frontend ops story. Payload covers the editorial side: TypeScript collections, a local admin, access control, and Lexical body content stored in Postgres.

I wanted one repo. Frontend routes and CMS schema live together. When a field changes, TypeScript complains in both places.

Content model before chrome

I defined collections before polishing the homepage.

  • Posts: title, slug, Lexical content, hero image, publishedAt, meta title/description, categories, authors, _status.
  • Categories / users / media: thin supporting collections.
  • Pages only where the layout is not a post.

The mistake I almost made was stuffing the homepage into one giant CMS body field. Instead, structured blocks and relations keep listing pages fast and keep SEO fields first-class.

App Router wiring

Public routes read published posts through Payload Local API or cached queries. The admin stays on Payload's panel. Draft preview matters: editors need to see an unpublished post without flipping _status to published.

For listings I select only card fields (title, slug, date, description, hero). I do not pull full Lexical JSON for index pages. That single habit keeps Postgres and TTFB honest as the archive grows.

Media and SEO

Every post gets:

  • A hero used in the article layout and OG image path
  • meta.title / meta.description distinct from the H1 when needed
  • Stable slugs; no casual renames after publish

Images go through Next.js Image with explicit sizes. Layout shift from unbound heroes is an easy CLS foot-gun on a portfolio.

Deploy and env

Production needs POSTGRES_URL, PAYLOAD_SECRET, and the public server URL. I publish and revalidate by slug so ISR/CDN caches do not serve stale bodies after a CMS update. A successful publish is a live URL returning HTTP 200 with the expected title, a green admin save alone.

What I would repeat

  1. Schema-first, UI second.
  2. Separate card queries from article queries.
  3. Treat revalidation as part of publish, not an afterthought.
  4. Keep the admin boring so writing stays easy.

What I would avoid

  • One unstructured body for the entire site
  • Client-fetching the whole archive on the homepage
  • Renaming slugs after they rank
  • Skipping meta fields because "the title is enough"

Next.js and Payload are a strong personal-site pair when you treat the CMS as product infrastructure. The stack is not the point. A site you can update safely on a Friday afternoon is.

Share this article

How I Built My Personal Website Using Nextjs with Payload | Mehd.ir