create-next-mdx-blog-app 2.2.0 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +403 -413
- package/package.json +89 -87
package/README.md
CHANGED
|
@@ -1,413 +1,403 @@
|
|
|
1
|
-
# 🌟 Next-MDX-Blog-Starter
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
###
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
The
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
-
|
|
189
|
-
-
|
|
190
|
-
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
- `src/
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
##
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
-
|
|
322
|
-
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
> **Note:**
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
- [
|
|
403
|
-
- [
|
|
404
|
-
- [Lee Robinson's Next.js + MDX Tutorial Video](https://www.youtube.com/watch?v=34bRv6cQezo&ab_channel=leerob)
|
|
405
|
-
- [MDX Playground](https://mdxjs.com/playground/)
|
|
406
|
-
- [React Documentation](https://reactjs.org/docs/getting-started.html)
|
|
407
|
-
- [Supabase Documentation](https://supabase.com/docs)
|
|
408
|
-
- [Docker Documentation](https://docs.docker.com/)
|
|
409
|
-
- [React Syntax Highlighter Package](https://github.com/react-syntax-highlighter/react-syntax-highlighter)
|
|
410
|
-
- [Sandpack Documentation](https://sandpack.codesandbox.io/docs)
|
|
411
|
-
- [Vercel AI SDK Documentation](https://sdk.vercel.ai/docs)
|
|
412
|
-
- [Anthropic AI SDK Provider](https://sdk.vercel.ai/providers/ai-sdk-providers/anthropic)
|
|
413
|
-
- [Zod Documentation](https://zod.dev)
|
|
1
|
+
# 🌟 Next-MDX-Blog-Starter
|
|
2
|
+
|
|
3
|
+
A production-ready technical blog starter kit built with **Next.js (App Router)**, **MDX**, **Supabase**, and **TypeScript strict mode**. SSG-first for performance and SEO, with AI-powered reader features, an in-browser code sandbox, and a full content-management toolchain.
|
|
4
|
+
|
|
5
|
+
**Live demo**: [next-mdx-blog-starter-sigma.vercel.app](https://next-mdx-blog-starter-sigma.vercel.app/)
|
|
6
|
+
|
|
7
|
+
This project was developed with the help of **Claude** (Anthropic) and **Cursor**, with design inspiration from **Lovable**.
|
|
8
|
+
|
|
9
|
+
## 📑 Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Quick Start](#%EF%B8%8F-quick-start)
|
|
12
|
+
- [Environment Variables](#-environment-variables)
|
|
13
|
+
- [Architecture & Core Stack](#%EF%B8%8F-architecture--core-stack)
|
|
14
|
+
- [MDX Content System](#-mdx-content-system)
|
|
15
|
+
- [Custom MDX Components](#-custom-mdx-components)
|
|
16
|
+
- [Reader Experience Features](#-reader-experience-features)
|
|
17
|
+
- [AI Features](#-ai-features)
|
|
18
|
+
- [Code Sandbox](#%EF%B8%8F-code-sandbox)
|
|
19
|
+
- [Author Profile Pages](#-author-profile-pages)
|
|
20
|
+
- [Newsletter Subscription](#-newsletter-subscription)
|
|
21
|
+
- [Supabase Database](#%EF%B8%8F-supabase-database)
|
|
22
|
+
- [Article Manager CLI](#%EF%B8%8F-article-manager-cli)
|
|
23
|
+
- [Project Scripts](#-project-scripts)
|
|
24
|
+
- [AWS S3 Image Storage](#%EF%B8%8F-aws-s3-image-storage)
|
|
25
|
+
- [SEO: Sitemap & Robots](#%EF%B8%8F-seo-sitemap--robots)
|
|
26
|
+
- [Mobile Responsiveness](#-mobile-responsiveness)
|
|
27
|
+
- [Analytics](#-analytics)
|
|
28
|
+
- [Docker](#-docker)
|
|
29
|
+
- [Useful Links](#-useful-links)
|
|
30
|
+
|
|
31
|
+
## ⚙️ Quick Start
|
|
32
|
+
|
|
33
|
+
### Prerequisites
|
|
34
|
+
- **Node.js** 18.17.0 or later (NPM included)
|
|
35
|
+
|
|
36
|
+
### Option 1 — NPM installer (recommended)
|
|
37
|
+
|
|
38
|
+
Scaffold the blog into an empty directory:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx create-next-mdx-blog-app .
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The installer ([`create-next-mdx-blog-app`](https://www.npmjs.com/package/create-next-mdx-blog-app/), v2.2.2, MIT) clones the app, installs dependencies, and prints the environment setup steps.
|
|
45
|
+
|
|
46
|
+
### Option 2 — Manual clone
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git clone https://github.com/CodingAbdullah/Next-MDX-Blog-Starter.git
|
|
50
|
+
cd Next-MDX-Blog-Starter/mdx-medium-blog-post-provider
|
|
51
|
+
npm install
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Configure and run
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cp .env.example .env.local # fill in your credentials (see table below)
|
|
58
|
+
npm run dev
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 🔐 Environment Variables
|
|
62
|
+
|
|
63
|
+
A `.env.example` file at the project root lists every key. Copy it to `.env.local` and fill in your values:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
cp .env.example .env.local
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# .env.local
|
|
71
|
+
|
|
72
|
+
# Supabase — dynamic blog post fetching
|
|
73
|
+
SUPABASE_URL=
|
|
74
|
+
SUPABASE_ANON_KEY=
|
|
75
|
+
|
|
76
|
+
# Anthropic — Blog Assistant chatbot + AI Article Summarizer
|
|
77
|
+
ANTHROPIC_API_KEY=
|
|
78
|
+
|
|
79
|
+
# Site — public base URL (sitemap, robots.txt, summarizer origin allow-list)
|
|
80
|
+
NEXT_PUBLIC_SITE_URL=
|
|
81
|
+
|
|
82
|
+
# GitHub — optional, for the <GitHubGist> component
|
|
83
|
+
GITHUB_TOKEN=
|
|
84
|
+
GITHUB_USERNAME=
|
|
85
|
+
GIST_BASE_URL=
|
|
86
|
+
|
|
87
|
+
# Resend — newsletter signup
|
|
88
|
+
RESEND_API_KEY=
|
|
89
|
+
RESEND_AUDIENCE_ID=
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Required
|
|
93
|
+
|
|
94
|
+
| Variable | Used by |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `SUPABASE_URL` | Dynamic blog post fetching (Supabase client) |
|
|
97
|
+
| `SUPABASE_ANON_KEY` | Dynamic blog post fetching (Supabase client) |
|
|
98
|
+
| `ANTHROPIC_API_KEY` | Blog Assistant chatbot (`/api/chat`) and AI Article Summarizer (`/api/summarize/[slug]`) |
|
|
99
|
+
| `RESEND_API_KEY` | Newsletter signup — authenticates with the Resend API |
|
|
100
|
+
| `RESEND_AUDIENCE_ID` | Newsletter signup — the Resend Audience new subscribers are added to |
|
|
101
|
+
|
|
102
|
+
### Optional (sensible defaults)
|
|
103
|
+
|
|
104
|
+
| Variable | Used by | Default |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `NEXT_PUBLIC_SITE_URL` | Sitemap, robots.txt, summarizer origin allow-list | `http://localhost:3000` |
|
|
107
|
+
| `GITHUB_TOKEN` | `<GitHubGist>` GitHub API auth — raises rate limit from 60 to 5,000 req/hr | unauthenticated |
|
|
108
|
+
| `GITHUB_USERNAME` | mdxgists.net URL construction | `CodingAbdullah` |
|
|
109
|
+
| `GIST_BASE_URL` | mdxgists.net link base URL | `https://mdxgists.net` |
|
|
110
|
+
|
|
111
|
+
> 🔒 **Never commit `.env` files.** They are git-ignored and excluded from Docker images via `.dockerignore`. Server-only secrets (Supabase keys, Anthropic and Resend API keys, GitHub token) are read exclusively in Server Components and Route Handlers — only `NEXT_PUBLIC_SITE_URL` is exposed to the browser.
|
|
112
|
+
|
|
113
|
+
## 🏗️ Architecture & Core Stack
|
|
114
|
+
|
|
115
|
+
This starter kit is **SSG-first**: Static Site Generation is preferred everywhere, with dynamic rendering used only where required.
|
|
116
|
+
|
|
117
|
+
- **Pre-rendered pages**: Blog posts and content pages are statically generated at build time
|
|
118
|
+
- **Server Components first**: Supabase is queried directly from React Server Components — no custom backend APIs
|
|
119
|
+
- **SEO-ready**: Meta tags, structured data, and semantic HTML generated statically
|
|
120
|
+
- **CDN-friendly**: Static output deploys to any CDN or static host
|
|
121
|
+
- **Minimal client JS**: Core content rendering requires no client-side JavaScript
|
|
122
|
+
|
|
123
|
+
| Layer | Technology |
|
|
124
|
+
|---|---|
|
|
125
|
+
| Framework | Next.js (App Router), React, TypeScript (strict mode) |
|
|
126
|
+
| Content | MDX, `gray-matter`, `next-mdx-remote` |
|
|
127
|
+
| Database | Supabase (`@supabase/supabase-js`) |
|
|
128
|
+
| Image storage | AWS S3 (via Next `<Image>`) |
|
|
129
|
+
| Styling / UI | Tailwind CSS, shadcn/ui, `lucide-react`, `class-variance-authority` |
|
|
130
|
+
| AI | Vercel AI SDK (`ai`, `@ai-sdk/anthropic`, `@ai-sdk/react`), `zod` |
|
|
131
|
+
| Code sandbox | `@codesandbox/sandpack-react` |
|
|
132
|
+
| Syntax highlighting | `react-syntax-highlighter` |
|
|
133
|
+
| Email | Resend (`resend`) |
|
|
134
|
+
| Notifications | `sonner` toasts |
|
|
135
|
+
| Theming | `next-themes` |
|
|
136
|
+
| Tooling | Docker, Vercel Analytics, `tsx`, `react-doctor` (`npm run doctor`) |
|
|
137
|
+
|
|
138
|
+
## 🌐 MDX Content System
|
|
139
|
+
|
|
140
|
+
This project renders blog posts from `.mdx` files in two modes, each with an included example:
|
|
141
|
+
|
|
142
|
+
- **Static MDX** — pre-rendered at build time. Route: `/sample-blog-post-page`, content: `src/markdown/ArticleContent.mdx`
|
|
143
|
+
- **Dynamic MDX** — rendered on the server at request time via `next-mdx-remote`'s `MDXRemote` component. Route: `/dynamic/[dynamic_blog_post]`, content: `src/markdown/DynamicArticleContent.mdx`
|
|
144
|
+
|
|
145
|
+
### Frontmatter (article metadata)
|
|
146
|
+
|
|
147
|
+
Each `.mdx` file carries metadata at the top of the file between `---` delimiters, parsed with `gray-matter`:
|
|
148
|
+
|
|
149
|
+
```yaml
|
|
150
|
+
---
|
|
151
|
+
title: string
|
|
152
|
+
description: string
|
|
153
|
+
publishedAt: string
|
|
154
|
+
author: string
|
|
155
|
+
tags: string[]
|
|
156
|
+
image: string
|
|
157
|
+
---
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### The `mdx-components.tsx` file
|
|
161
|
+
|
|
162
|
+
`mdx-components.tsx` at the project root styles built-in HTML elements and swaps `<img>` and `<a>` for the optimized Next.js `<Image>` and `<Link>` components. See the [Next.js docs](https://nextjs.org/docs/app/api-reference/file-conventions/mdx-components) for how this file convention works.
|
|
163
|
+
|
|
164
|
+
## 🧩 Custom MDX Components
|
|
165
|
+
|
|
166
|
+
Custom components for MDX content live in `src/components/customMDXComponents/`.
|
|
167
|
+
|
|
168
|
+
### CodeBlock — syntax highlighting
|
|
169
|
+
|
|
170
|
+
Highlights code blocks with `react-syntax-highlighter` (default theme: `vscDarkPlus` — [demo of available themes](https://react-syntax-highlighter.github.io/react-syntax-highlighter/demo/)). A **copy button** in the top-right corner writes the code to the clipboard and confirms via a sonner toast.
|
|
171
|
+
|
|
172
|
+
### GitHubGist — live gist embeds
|
|
173
|
+
|
|
174
|
+
Embed public GitHub Gists directly in MDX:
|
|
175
|
+
|
|
176
|
+
```mdx
|
|
177
|
+
<GitHubGist id="your-gist-id" figCaptionText="Caption shown below the gist" />
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**How it works:**
|
|
181
|
+
1. `GitHubGist` (an async Server Component) calls `https://api.github.com/gists/<id>` for metadata (file name, detected language), then fetches the raw content from the returned `raw_url`. Results are cached for one hour via Next.js ISR (`next: { revalidate: 3600 }`).
|
|
182
|
+
2. The language is mapped to a Prism-compatible token via `GITHUB_GIST_LANGUAGE_MAP` (`src/utils/constants/GitHubGistConstants.ts`) — 70+ languages supported, with sensible fallbacks (`language.toLowerCase()`, then `text`).
|
|
183
|
+
3. The code renders through `GistCodeBlock` inside a scrollable container with a custom green scrollbar.
|
|
184
|
+
|
|
185
|
+
**Each gist header includes:** a language badge, a **Copy** button (with toast confirmation), and a link to the gist on [mdxgists.net](https://mdxgists.net) — a companion site for browsing and sharing MDX-ready gists. The URL is built as `<GIST_BASE_URL>/<GITHUB_USERNAME>/<gist-id>`; both values are overridable via environment variables.
|
|
186
|
+
|
|
187
|
+
**Components:**
|
|
188
|
+
- `GitHubGist.tsx` — async Server Component; fetching, language resolution, gist UI
|
|
189
|
+
- `GistCodeBlock.tsx` — syntax-highlighted code display
|
|
190
|
+
- `GistCopyButton.tsx` — client component; copy-to-clipboard + external link
|
|
191
|
+
|
|
192
|
+
**GitHub token (optional but recommended):** unauthenticated GitHub API requests are limited to **60/hr per IP**. Set `GITHUB_TOKEN` in `.env.local` to raise this to **5,000/hr**. Generate one at GitHub → Settings → Developer settings → Personal access tokens — the read-only `gist` scope (or no scopes for public gists) is sufficient.
|
|
193
|
+
|
|
194
|
+
> Only **public** gists are supported by default; the component can be extended for private gists with appropriate token scopes.
|
|
195
|
+
|
|
196
|
+
### MDXImage — optimized images with captions
|
|
197
|
+
|
|
198
|
+
Wraps the Next.js `<Image>` component with `<figure>`/`<figcaption>` for seamless captions, displayed in a styled container with a green glow border and hover scale effect consistent with the matrix-green design system.
|
|
199
|
+
|
|
200
|
+
## 🎨 Reader Experience Features
|
|
201
|
+
|
|
202
|
+
| Feature | Component | Behaviour |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| **Light/dark mode** | `src/components/ThemeToggle.tsx` | Header toggle between dark (default) and light themes via `next-themes` (`ThemeProvider.tsx`); persisted to `localStorage`. `ThemedToaster.tsx` keeps sonner toasts theme-matched |
|
|
205
|
+
| **Reading progress bar** | `src/components/ReadingProgressBar.tsx` | Thin green bar fixed to the top of article pages; passive scroll listener, `role="progressbar"` ARIA attributes |
|
|
206
|
+
| **Back to top** | `src/components/BackToTopButton.tsx` | Floating button appears after 400px of scroll; smooth-scrolls to top |
|
|
207
|
+
| **Copy link** | `src/components/CopyLinkButton.tsx` | Article-header button; copies the page URL with a brief icon-swap confirmation |
|
|
208
|
+
| **Social share** | `src/components/SocialShareButtons.tsx` | Pre-filled share dialogs for X (Twitter), LinkedIn, and Reddit using the article title and URL |
|
|
209
|
+
| **View counter** | `src/components/ViewCounter.tsx` | Live per-article view count in a Supabase `view_counts` table, incremented atomically via the `increment_view_count` RPC. Dynamic pages increment server-side; static pages call the `/api/views/[slug]` route after hydration |
|
|
210
|
+
|
|
211
|
+
## 🤖 AI Features
|
|
212
|
+
|
|
213
|
+
Both AI features use **Claude Haiku** through the Vercel AI SDK and require `ANTHROPIC_API_KEY`.
|
|
214
|
+
|
|
215
|
+
### Blog Assistant Chatbot (`/chat`)
|
|
216
|
+
|
|
217
|
+
An interactive AI chatbot optimised for readers of this technical blog.
|
|
218
|
+
|
|
219
|
+
- **Streaming responses** via `TextStreamChatTransport` and an edge API route (`src/app/api/chat/route.ts`), which converts `UIMessages` with `convertToModelMessages` and streams via `streamText`
|
|
220
|
+
- **Three blog-focused tools** available to the model:
|
|
221
|
+
- `searchBlogTopics` — searches a curated list of topics covered by this blog (JS, TS, React, Next.js, MDX, Docker, AWS, etc.)
|
|
222
|
+
- `getCodeExample` — generates a runnable code example for a concept and language
|
|
223
|
+
- `explainConcept` — produces a brief or detailed technical explanation
|
|
224
|
+
- **Ephemeral conversations** — nothing is persisted server-side; a "New Chat" button clears the session
|
|
225
|
+
- **Tool calls rendered inline** in the message thread via `chat-tool-result.tsx`
|
|
226
|
+
|
|
227
|
+
Configuration lives in `src/utils/constants/AiChatConfig.ts` (model, temperature, max tokens, system prompt) and `ToolConfig.ts` (tool icons/labels/colours). UI components are under `src/components/chat/` (`chat-interface.tsx`, `chat-messages.tsx`, `chat-input.tsx`, `chat-tool-result.tsx`).
|
|
228
|
+
|
|
229
|
+
### AI Article Summarizer
|
|
230
|
+
|
|
231
|
+
A **Generate TL;DR** button above every dynamic article streams a short summary into a collapsible green panel.
|
|
232
|
+
|
|
233
|
+
- Only the article **slug** is sent over the wire — `src/app/api/summarize/[slug]/route.ts` (edge) fetches the article server-side
|
|
234
|
+
- Protected by an **origin allow-list** (`NEXT_PUBLIC_SITE_URL`), a **per-IP rate limit** (10/min), and a **1-hour in-memory cache** (written via `after()`)
|
|
235
|
+
- The client (`src/components/ArticleSummarizer.tsx`) cancels in-flight requests with `AbortController` on close/unmount
|
|
236
|
+
- Model and prompt configured in `src/utils/constants/AiSummaryConfig.ts`
|
|
237
|
+
|
|
238
|
+
## 🖥️ Code Sandbox
|
|
239
|
+
|
|
240
|
+
An interactive in-browser code execution environment powered by **Sandpack** at `/code-sandbox`.
|
|
241
|
+
|
|
242
|
+
- **JavaScript & TypeScript runtimes** — switch with one click; the editor reloads with a language-appropriate default snippet
|
|
243
|
+
- **Quick Examples** — pre-loaded snippets (Fibonacci, Async/Await, Data Structures for JS; Types & Interfaces, Generics, Classes & OOP for TS) defined in `src/utils/constants/SandboxExamples.ts`
|
|
244
|
+
- **Full editor** — line numbers, inline TypeScript errors, matrix-green custom theme with `JetBrains Mono`
|
|
245
|
+
- **Toolbar** — Run, Copy, Download, and Reset
|
|
246
|
+
- **Live console panel** side-by-side with the editor — execution is **entirely in-browser**; no code reaches the server
|
|
247
|
+
|
|
248
|
+
Components: `src/components/SandpackEditor.tsx` (provider, editor, toolbar, console) and `CodeSandboxFeaturesSection.tsx` (feature highlights grid).
|
|
249
|
+
|
|
250
|
+
## 👥 Author Profile Pages
|
|
251
|
+
|
|
252
|
+
A `/authors` index lists every distinct contributor pulled from the Supabase `Article` table, with per-author profile routes at `/authors/[slug]` showing their bio, avatar, and every article they've published.
|
|
253
|
+
|
|
254
|
+
## 📬 Newsletter Subscription
|
|
255
|
+
|
|
256
|
+
An integrated newsletter signup form powered by [Resend](https://resend.com). Visitors can subscribe from the home page or the bottom of any article; new subscribers are added to a Resend **Audience** and receive a welcome email.
|
|
257
|
+
|
|
258
|
+
### Components & routes
|
|
259
|
+
- `src/components/NewsletterSignup.tsx` — reusable client component with `matrix` (article pages) and `neutral` (home page) visual variants
|
|
260
|
+
- `src/app/api/newsletter/subscribe/route.ts` — Node-runtime POST handler; validates the email with Zod
|
|
261
|
+
- `src/utils/functions/newsletter/subscribeToNewsletter.ts` — adds the contact to your Audience and triggers the welcome email
|
|
262
|
+
- `src/utils/functions/newsletter/welcomeEmailTemplate.ts` — subject + HTML/text bodies
|
|
263
|
+
- `src/utils/functions/resend_client/ResendClient.ts` — server-only Resend client factory
|
|
264
|
+
|
|
265
|
+
### Setup
|
|
266
|
+
1. Create a Resend account at [resend.com](https://resend.com)
|
|
267
|
+
2. **API key** — API Keys → Create API Key with **Sending** and **Audiences** access → set `RESEND_API_KEY`
|
|
268
|
+
3. **Audience** — Audiences → Create Audience → copy the UUID into `RESEND_AUDIENCE_ID`
|
|
269
|
+
4. Restart `npm run dev` so Next.js picks up the new variables
|
|
270
|
+
|
|
271
|
+
### ⚠️ ***Important: a verified sending domain on Resend is required for email delivery to real subscribers.***
|
|
272
|
+
|
|
273
|
+
Out of the box the welcome email is sent from **`onboarding@resend.dev`**, Resend's sandbox sender, which **only delivers to the email address that owns your Resend account**.
|
|
274
|
+
|
|
275
|
+
> 🚨 ***You MUST verify your own sending domain (Resend dashboard → Domains → Add Domain, then complete the SPF/DKIM DNS records) before the newsletter can deliver welcome emails to real subscribers.*** Once verified, update the `WELCOME_EMAIL_FROM` constant in `src/utils/functions/newsletter/subscribeToNewsletter.ts` to an address on that domain (e.g. `newsletter@yourdomain.com`).
|
|
276
|
+
|
|
277
|
+
If the domain step is skipped, the contact is still added to your Audience, but the welcome email silently fails (logged server-side as `[newsletter] Resend emails.send returned an error: ...`).
|
|
278
|
+
|
|
279
|
+
## 🗄️ Supabase Database
|
|
280
|
+
|
|
281
|
+
Supabase is integrated **directly in Server Components** — no custom backend API layer. The client factory lives in `src/utils/functions/supabase_client/`.
|
|
282
|
+
|
|
283
|
+
- **CRUD functions** — `src/utils/functions/crud/` (create, read, update, delete articles)
|
|
284
|
+
- **RPC functions** — `src/utils/functions/rpc/` (e.g. `incrementViewCount`)
|
|
285
|
+
- **SQL scripts** — `scripts/sql/DDL/` (create/alter/drop tables, view-counts table, RPC function) and `scripts/sql/DML/` (insert/fetch/update/delete article statements)
|
|
286
|
+
|
|
287
|
+
**Ensure your Supabase Row Level Security policies allow the CRUD actions you need** — these are configured in the Supabase console.
|
|
288
|
+
|
|
289
|
+
Shared constants, helper functions, and TypeScript types live under `src/utils/` (`constants/`, `functions/`, `types/`). Some constants serve as placeholders for this demo application.
|
|
290
|
+
|
|
291
|
+
## ⌨️ Article Manager CLI
|
|
292
|
+
|
|
293
|
+
`scripts/action-script/article-manager.ts` manages articles in Supabase from the command line using the CRUD functions above, run via `tsx` (no TypeScript build config needed):
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
# Insert an article into Supabase
|
|
297
|
+
npx tsx article-manager.ts insert article-slug
|
|
298
|
+
|
|
299
|
+
# Delete an article
|
|
300
|
+
npx tsx article-manager.ts delete article-slug
|
|
301
|
+
|
|
302
|
+
# Update an article
|
|
303
|
+
npx tsx article-manager.ts update article-slug new-article-file
|
|
304
|
+
|
|
305
|
+
# Fetch a single article
|
|
306
|
+
npx tsx article-manager.ts fetch article-slug
|
|
307
|
+
|
|
308
|
+
# Fetch all articles
|
|
309
|
+
npx tsx article-manager.ts fetchAll
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The **article slug** is the name of the markdown file in `src/markdown/` minus the `.mdx` extension. The `update` command's second argument follows the same convention.
|
|
313
|
+
|
|
314
|
+
## 📜 Project Scripts
|
|
315
|
+
|
|
316
|
+
The `/scripts` folder contains tooling for project and database setup:
|
|
317
|
+
|
|
318
|
+
- **`scripts/sql/`** — DDL and DML statements for initializing and populating the database
|
|
319
|
+
- **`scripts/powershell/`** — Windows setup plus bulk article/S3 helpers (`project_setup.ps1`, `bulk-create-articles.ps1`, `bulk-update-articles.ps1`, `bulk-s3-folder-creation.ps1`, `create-missing-mdx-files.ps1`)
|
|
320
|
+
- **`scripts/bash/`** — the same toolkit for Linux/macOS
|
|
321
|
+
- **`scripts/action-script/`** — the Article Manager CLI (above)
|
|
322
|
+
- **`scripts/github-gist-fetcher/`** — fetches a public gist's raw content to `src/github_gists/<gist-id>.txt`:
|
|
323
|
+
```bash
|
|
324
|
+
npx tsx scripts/github-gist-fetcher/fetch-github-gist.ts <gist-id>
|
|
325
|
+
```
|
|
326
|
+
> **Note:** no longer required for rendering — `<GitHubGist>` fetches live from the GitHub API. Retained for offline inspection or local caching workflows.
|
|
327
|
+
|
|
328
|
+
## 🌩️ AWS S3 Image Storage
|
|
329
|
+
|
|
330
|
+
Images referenced in `.mdx` files (via the `MDXImage` component) are served from AWS S3. The allowed external image domain is configured in `next.config.ts` — point it at your own bucket, or swap in another storage service entirely.
|
|
331
|
+
|
|
332
|
+
## 🗺️ SEO: Sitemap & Robots
|
|
333
|
+
|
|
334
|
+
- **`sitemap.xml`** — auto-generated at build time by `src/app/sitemap.ts`, covering all static routes and every dynamic article slug fetched from Supabase
|
|
335
|
+
- **`robots.txt`** — served from `src/app/robots.ts`, permitting all crawlers and pointing them at the sitemap
|
|
336
|
+
|
|
337
|
+
Both use `NEXT_PUBLIC_SITE_URL` as the base URL.
|
|
338
|
+
|
|
339
|
+
## 📱 Mobile Responsiveness
|
|
340
|
+
|
|
341
|
+
All components are built mobile-first with Tailwind responsive prefixes (`sm:`, `md:`, `lg:`). Key behaviours:
|
|
342
|
+
|
|
343
|
+
| Component | Mobile behaviour |
|
|
344
|
+
|---|---|
|
|
345
|
+
| `ArticleHeader` | Title scales `text-2xl` → `text-4xl`; author row stacks vertically |
|
|
346
|
+
| `ArticleAuthorBio` | Avatar and text stack vertically; full-width button; center-aligned text |
|
|
347
|
+
| `GitHubGist` | Header wraps via `flex-wrap`; code scrolls horizontally with a styled green scrollbar |
|
|
348
|
+
| `CodeBlock` | Copy button absolutely positioned, never interferes with code flow |
|
|
349
|
+
| `MDXImage` | Container scales inline; hover effect on capable devices |
|
|
350
|
+
| Chat & Code Sandbox | Responsive padding and height calculations across breakpoints |
|
|
351
|
+
|
|
352
|
+
The root layout exports a `viewport` configuration (Next.js `Viewport` type) for correct scaling on all mobile browsers:
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
export const viewport: Viewport = {
|
|
356
|
+
width: 'device-width',
|
|
357
|
+
initialScale: 1,
|
|
358
|
+
};
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## 📊 Analytics
|
|
362
|
+
|
|
363
|
+
Vercel Analytics (`@vercel/analytics`) is integrated to track user interactions and performance metrics — kept lightweight and non-blocking.
|
|
364
|
+
|
|
365
|
+
## 🐳 Docker
|
|
366
|
+
|
|
367
|
+
A multi-stage `Dockerfile` at the project root produces a production image with environment variables injected at runtime:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
docker build -t mdx-medium-blog .
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
docker run \
|
|
375
|
+
-e SUPABASE_URL=your_supabase_url \
|
|
376
|
+
-e SUPABASE_ANON_KEY=your_supabase_anon_key \
|
|
377
|
+
-e ANTHROPIC_API_KEY=your_anthropic_api_key \
|
|
378
|
+
-e NEXT_PUBLIC_SITE_URL=https://yourdomain.com \
|
|
379
|
+
-e GITHUB_TOKEN=your_github_token \
|
|
380
|
+
-e GITHUB_USERNAME=your_github_username \
|
|
381
|
+
-e GIST_BASE_URL=https://mdxgists.net \
|
|
382
|
+
-e RESEND_API_KEY=your_resend_api_key \
|
|
383
|
+
-e RESEND_AUDIENCE_ID=your_resend_audience_id \
|
|
384
|
+
-p 3000:3000 mdx-medium-blog
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
The project also remains compatible with **Vercel** and pure **static hosting** (SSG mode).
|
|
388
|
+
|
|
389
|
+
## 🔗 Useful Links
|
|
390
|
+
|
|
391
|
+
- [Next.js Documentation](https://nextjs.org/docs)
|
|
392
|
+
- [MDX Documentation](https://mdxjs.com/docs/)
|
|
393
|
+
- [Lee Robinson's Next.js + MDX Tutorial Video](https://www.youtube.com/watch?v=34bRv6cQezo&ab_channel=leerob)
|
|
394
|
+
- [MDX Playground](https://mdxjs.com/playground/)
|
|
395
|
+
- [React Documentation](https://react.dev/)
|
|
396
|
+
- [Supabase Documentation](https://supabase.com/docs)
|
|
397
|
+
- [Docker Documentation](https://docs.docker.com/)
|
|
398
|
+
- [React Syntax Highlighter](https://github.com/react-syntax-highlighter/react-syntax-highlighter)
|
|
399
|
+
- [Sandpack Documentation](https://sandpack.codesandbox.io/docs)
|
|
400
|
+
- [Vercel AI SDK Documentation](https://sdk.vercel.ai/docs)
|
|
401
|
+
- [Anthropic AI SDK Provider](https://sdk.vercel.ai/providers/ai-sdk-providers/anthropic)
|
|
402
|
+
- [Resend Documentation](https://resend.com/docs)
|
|
403
|
+
- [Zod Documentation](https://zod.dev)
|
package/package.json
CHANGED
|
@@ -1,87 +1,89 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "create-next-mdx-blog-app",
|
|
3
|
-
"version": "2.2.
|
|
4
|
-
"description": "A production-ready technical blog starter kit built with Next.js, MDX, Supabase, and TypeScript.",
|
|
5
|
-
"author": {
|
|
6
|
-
"name": "Abdullah Muhammad",
|
|
7
|
-
"url": "https://github.com/CodingAbdullah"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"bin/"
|
|
11
|
-
],
|
|
12
|
-
"bin": {
|
|
13
|
-
"create-next-mdx-blog-app": "./bin/create.js"
|
|
14
|
-
},
|
|
15
|
-
"engines": {
|
|
16
|
-
"node": ">=18.17.0"
|
|
17
|
-
},
|
|
18
|
-
"homepage": "https://next-mdx-blog-starter-sigma.vercel.app/",
|
|
19
|
-
"repository": {
|
|
20
|
-
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/CodingAbdullah/Next-MDX-Blog-Starter.git"
|
|
22
|
-
},
|
|
23
|
-
"keywords": [
|
|
24
|
-
"aws",
|
|
25
|
-
"docker",
|
|
26
|
-
"javascript",
|
|
27
|
-
"jsx",
|
|
28
|
-
"mdx",
|
|
29
|
-
"next",
|
|
30
|
-
"react",
|
|
31
|
-
"sql",
|
|
32
|
-
"supabase",
|
|
33
|
-
"typescript"
|
|
34
|
-
],
|
|
35
|
-
"license": "MIT",
|
|
36
|
-
"scripts": {
|
|
37
|
-
"dev": "next dev",
|
|
38
|
-
"build": "next build",
|
|
39
|
-
"start": "next start",
|
|
40
|
-
"lint": "next lint"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"@ai-sdk/
|
|
50
|
-
"@
|
|
51
|
-
"@
|
|
52
|
-
"@
|
|
53
|
-
"@mdx-js/
|
|
54
|
-
"@
|
|
55
|
-
"@
|
|
56
|
-
"@radix-ui/react-
|
|
57
|
-
"@
|
|
58
|
-
"@
|
|
59
|
-
"@
|
|
60
|
-
"@types/
|
|
61
|
-
"@types/
|
|
62
|
-
"@types/react
|
|
63
|
-
"@types/react-
|
|
64
|
-
"@
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"eslint
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"next
|
|
74
|
-
"next-
|
|
75
|
-
"
|
|
76
|
-
"react
|
|
77
|
-
"react-
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "create-next-mdx-blog-app",
|
|
3
|
+
"version": "2.2.2",
|
|
4
|
+
"description": "A production-ready technical blog starter kit built with Next.js, MDX, Supabase, and TypeScript.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Abdullah Muhammad",
|
|
7
|
+
"url": "https://github.com/CodingAbdullah"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/"
|
|
11
|
+
],
|
|
12
|
+
"bin": {
|
|
13
|
+
"create-next-mdx-blog-app": "./bin/create.js"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18.17.0"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://next-mdx-blog-starter-sigma.vercel.app/",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/CodingAbdullah/Next-MDX-Blog-Starter.git"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"aws",
|
|
25
|
+
"docker",
|
|
26
|
+
"javascript",
|
|
27
|
+
"jsx",
|
|
28
|
+
"mdx",
|
|
29
|
+
"next",
|
|
30
|
+
"react",
|
|
31
|
+
"sql",
|
|
32
|
+
"supabase",
|
|
33
|
+
"typescript"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"scripts": {
|
|
37
|
+
"dev": "next dev",
|
|
38
|
+
"build": "next build",
|
|
39
|
+
"start": "next start",
|
|
40
|
+
"lint": "next lint",
|
|
41
|
+
"doctor": "npx react-doctor@latest"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@inquirer/prompts": "^8.4.3",
|
|
45
|
+
"degit": "^3.0.0",
|
|
46
|
+
"execa": "^9.6.1"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@ai-sdk/anthropic": "^3.0.78",
|
|
50
|
+
"@ai-sdk/react": "^3.0.192",
|
|
51
|
+
"@codesandbox/sandpack-react": "^2.20.0",
|
|
52
|
+
"@eslint/eslintrc": "^3",
|
|
53
|
+
"@mdx-js/loader": "^3.1.1",
|
|
54
|
+
"@mdx-js/react": "^3.1.1",
|
|
55
|
+
"@next/mdx": "^16.2.6",
|
|
56
|
+
"@radix-ui/react-avatar": "^1.1.11",
|
|
57
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
58
|
+
"@supabase/supabase-js": "^2.106.1",
|
|
59
|
+
"@tailwindcss/postcss": "^4",
|
|
60
|
+
"@types/mdx": "^2.0.13",
|
|
61
|
+
"@types/node": "^25.9.1",
|
|
62
|
+
"@types/react": "^19",
|
|
63
|
+
"@types/react-dom": "^19",
|
|
64
|
+
"@types/react-syntax-highlighter": "^15.5.13",
|
|
65
|
+
"@vercel/analytics": "^2.0.1",
|
|
66
|
+
"ai": "^6.0.190",
|
|
67
|
+
"class-variance-authority": "^0.7.1",
|
|
68
|
+
"clsx": "^2.1.1",
|
|
69
|
+
"eslint": "^10",
|
|
70
|
+
"eslint-config-next": "16.2.6",
|
|
71
|
+
"gray-matter": "^4.0.3",
|
|
72
|
+
"lucide-react": "^1.16.0",
|
|
73
|
+
"next": "^16.2.6",
|
|
74
|
+
"next-mdx-remote": "^6.0.0",
|
|
75
|
+
"next-themes": "^0.4.6",
|
|
76
|
+
"react": "^19.2.6",
|
|
77
|
+
"react-doctor": "^0.2.14",
|
|
78
|
+
"react-dom": "^19.2.6",
|
|
79
|
+
"react-syntax-highlighter": "^16.1.1",
|
|
80
|
+
"resend": "^6.12.3",
|
|
81
|
+
"sonner": "^2.0.7",
|
|
82
|
+
"tailwind-merge": "^3.6.0",
|
|
83
|
+
"tailwindcss": "^4",
|
|
84
|
+
"tsx": "^4.22.3",
|
|
85
|
+
"tw-animate-css": "^1.4.0",
|
|
86
|
+
"typescript": "6.0.3",
|
|
87
|
+
"zod": "^4.4.3"
|
|
88
|
+
}
|
|
89
|
+
}
|