1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
Next blog
=========
A full-stack blog built with Next.js 15 and Payload CMS. Ships authentication,
comments, search, RSS, and an email template out of the box.
Overview
--------
Frontend uses Next.js App Router with Tailwind and shadcn, plus fumadocs layout
pieces for navigation, pagination, and search.
Content is managed by Payload CMS (payload.config.ts) with support for rich
text, tags, cover images, and publish states.
Authentication relies on better-auth with Google and GitHub; sessions and
comments are stored in Postgres through Drizzle ORM.
Comments use @fuma-comment, and the site generates both /rss.xml feeds and
/api/search indexes.
Features
--------
Post management: Payload admin (/admin) creates drafts or published posts with
cover images, tags, and scheduled publish time.
Blog experience: paginated lists, a tag hub, rich post detail rendering, a share
button, and one-click copy link.
Social and interaction: Google or GitHub login, better-auth session management,
Fuma Comment stored locally.
Enhancements: RSS feed, on-the-fly search index, automatic Open Graph banners,
and sitemap via next-sitemap.
Email template: emails/newsletter-welcome.tsx using React Email with Tailwind,
ready for Resend.
Ops friendly: .env validation, start-database.sh to boot local Postgres, unified
Drizzle and Payload scripts for schema.
Tech Stack
----------
Framework: Next.js 15 (App Router) with server components and hybrid
static/dynamic rendering.
UI: Tailwind CSS 4 with shadcn and fumadocs-ui for navigation, pagination,
sections, and theming.
Content: Payload CMS for Posts, Users, and Media with Lexical rich text.
Data: PostgreSQL and Drizzle ORM; auth and comments tables are prefixed blog_*.
Auth: better-auth with OAuth (Google/GitHub), sessions, and an extra role field.
Email: Resend with React Email, welcome template styled by Tailwind.
Quickstart
----------
I recommend using docker to run postgres.
Run the following command to start a Postgres instance:
docker run -d \
--name Next-blog \
-e POSTGRES_USER=admin \
-e POSTGRES_PASSWORD=YOUR_PASSWORD_HERE \
-e POSTGRES_DB=db \
-p 5432:5432 \
-v "$(pwd)/pgdata:/var/lib/postgresql/data" \
postgres:15.1
Then, copy .env.example to .env and fill in the required environment variables
that suit your setup.
Run: pnpm install
Run Payload CMS migrations: pnpm payload migrate
Run database migrations: pnpm db:push
(optional: SKIP_ENV_VALIDATION=1 pnpm db:push)
Start the development server: pnpm dev
(optional SKIP_ENV_VALIDATION=1 pnpm dev)
Open <http://localhost:3000>
Core Modules
------------
Content model lives in payload.config.ts, defining Posts, Users, and Media with
cover uploads, tag arrays, publish time, and status.
Data fetching is wrapped in src/lib/payload-posts.ts for post queries, tag
stats, pagination, and slug helpers.
Pages and layout reside under src/app/(main); _components includes Hero, CTA,
and post list UI.
Auth and comments sit in src/server/auth (better-auth + Drizzle) and
src/server/comments storing into blog_comments tables.
Search and feed live in src/app/(main)/api/search/route.ts (indexes) and
src/app/(main)/rss.xml/route.ts (Atom/RSS).
Email template is emails/newsletter-welcome.tsx, a React Email component that
accepts a posts array.
Project structure (excerpt)
---------------------------
src/
app/
(main)/(home)/page.tsx Home with hero, recent posts, CTA
(main)/(home)/posts/[slug]/page.tsx Post detail with comments and share
(main)/(home)/posts/page.tsx Paginated posts list
(main)/(home)/tags/page.tsx Tag hub
(main)/api/search/route.ts Search index API
(main)/rss.xml/route.ts Atom/RSS feed
(payload)/admin/... Payload CMS admin
lib/ Data and utilities (payload-posts,
metadata, auth-client)
server/ better-auth and Drizzle schema plus
comment storage
emails/ React Email templates
Environment variables
---------------------
Required or commonly used:
DATABASE_URL: Postgres connection string, e.g.
postgresql://user:pass@localhost:5432/blog
PAYLOAD_SECRET: Secret used by Payload CMS for JWT
BETTER_AUTH_SECRET: better-auth session secret
BETTER_AUTH_URL: Auth callback base, local is <http://localhost:3000>
NEXT_PUBLIC_SERVER_URL: Base URL for links, OG, and RSS
RESEND_API_KEY / RESEND_AUDIENCE_ID / EMAIL_FROM: Needed for sending emails
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET: Needed if enabling Google login
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET: Needed if enabling GitHub login
NEXT_PUBLIC_UMAMI_URL / NEXT_PUBLIC_UMAMI_WEBSITE_ID: Optional analytics
Scripts
-------
pnpm dev Start Next.js dev server (includes Payload routes).
pnpm build Build production output.
pnpm preview Preview the production build.
pnpm db:generate Generate Drizzle migrations for auth and comments.
pnpm db:migrate Run Drizzle migrations.
pnpm payload:migrate Sync Payload CMS tables.
pnpm lint Run quality checks (biome).
pnpm format Format source (biome).
pnpm email:dev Preview React Email templates locally.
Routes and APIs
---------------
/ Home with hero, latest posts, and CTA.
/posts Paginated posts; /posts/[slug] for detail with rich text,
comments, and share.
/tags Tag cloud with counts.
/login Google or GitHub login entry.
/admin Payload CMS admin.
/api/search Search index endpoint (fumadocs search).
/rss.xml Atom/RSS feed.
Developer notes
---------------
Prefer ./start-database.sh to boot Postgres locally; the script warns if the
port is in use.
Auth and comment tables use the blog_prefix; keep drizzle.config.ts tablesFilter
in sync.
Payload dev mode pre-fills <admin@example.com> / admin123 to speed up admin
login.
If you only need to browse the UI without real data, set SKIP_ENV_VALIDATION=1
and disable features needing external services; full env is recommended for
complete coverage.
|