create-next-mdx-blog-app 1.0.0
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/Dockerfile +37 -0
- package/README.md +182 -0
- package/cli.js +52 -0
- package/components.json +21 -0
- package/eslint.config.mjs +16 -0
- package/mdx-components.tsx +208 -0
- package/next.config.ts +22 -0
- package/package.json +49 -0
- package/postcss.config.mjs +5 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/action-script/article-manager.ts +99 -0
- package/scripts/bash/project_setup.sh +19 -0
- package/scripts/powershell/project_setup.ps1 +18 -0
- package/scripts/sql/DDL/alterTable.sql +10 -0
- package/scripts/sql/DDL/createTable.sql +16 -0
- package/scripts/sql/DDL/dropTable.sql +3 -0
- package/scripts/sql/DDL/truncateTable.sql +2 -0
- package/scripts/sql/DML/deleteArticle.sql +6 -0
- package/scripts/sql/DML/fetchArticle.sql +15 -0
- package/scripts/sql/DML/insertArticle.sql +31 -0
- package/scripts/sql/DML/updateArticle.sql +12 -0
- package/src/app/dynamic/[dynamic_blog_post]/error.tsx +31 -0
- package/src/app/dynamic/[dynamic_blog_post]/page.tsx +18 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +159 -0
- package/src/app/layout.tsx +24 -0
- package/src/app/not-found.tsx +29 -0
- package/src/app/page.tsx +57 -0
- package/src/app/sample-blog-post-page/page.tsx +24 -0
- package/src/components/ArticleAuthorBio.tsx +27 -0
- package/src/components/ArticleCoverImage.tsx +17 -0
- package/src/components/ArticleHeader.tsx +40 -0
- package/src/components/DynamicArticle.tsx +32 -0
- package/src/components/MDXRemoteArticle.tsx +16 -0
- package/src/components/StaticArticle.tsx +14 -0
- package/src/components/customMDXComponents/CodeBlock.tsx +19 -0
- package/src/components/customMDXComponents/GitHubGist.tsx +48 -0
- package/src/components/customMDXComponents/MDXImage.tsx +21 -0
- package/src/components/ui/avatar.tsx +53 -0
- package/src/components/ui/badge.tsx +46 -0
- package/src/components/ui/button.tsx +59 -0
- package/src/lib/utils.ts +6 -0
- package/src/markdown/ArticleContent.mdx +108 -0
- package/src/markdown/DynamicArticleContent.mdx +58 -0
- package/src/utils/constants/ArticleAuthorInfoList.ts +10 -0
- package/src/utils/constants/ArticleHeaderInfoList.ts +18 -0
- package/src/utils/functions/crud/deleteArticle.ts +17 -0
- package/src/utils/functions/crud/fetchAllArticles.ts +16 -0
- package/src/utils/functions/crud/fetchArticle.ts +26 -0
- package/src/utils/functions/crud/insertArticle.ts +45 -0
- package/src/utils/functions/crud/updateArticle.ts +26 -0
- package/src/utils/functions/supabase_client/SupabaseClient.ts +13 -0
- package/src/utils/types/ArticleAuthorInfoType.ts +6 -0
- package/src/utils/types/ArticleCoverImageType.ts +4 -0
- package/src/utils/types/ArticleHeaderInfoType.ts +13 -0
- package/src/utils/types/GitHubGistType.ts +5 -0
- package/src/utils/types/MDXImageType.ts +8 -0
- package/tsconfig.json +39 -0
package/Dockerfile
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Dockerize the Next.js application and run a production version in a container using this Dockerfile
|
|
2
|
+
# Build stage
|
|
3
|
+
FROM node:20-alpine AS builder
|
|
4
|
+
|
|
5
|
+
WORKDIR /app
|
|
6
|
+
|
|
7
|
+
# Copy package files
|
|
8
|
+
COPY package*.json ./
|
|
9
|
+
|
|
10
|
+
# Install dependencies
|
|
11
|
+
RUN npm ci
|
|
12
|
+
|
|
13
|
+
# Copy the rest of the application
|
|
14
|
+
COPY . .
|
|
15
|
+
|
|
16
|
+
# Build the application
|
|
17
|
+
RUN npm run build
|
|
18
|
+
|
|
19
|
+
# Production stage
|
|
20
|
+
FROM node:20-alpine AS runner
|
|
21
|
+
|
|
22
|
+
WORKDIR /app
|
|
23
|
+
|
|
24
|
+
# Set environment variables
|
|
25
|
+
ENV NODE_ENV=production
|
|
26
|
+
|
|
27
|
+
# Copy necessary files from builder
|
|
28
|
+
COPY --from=builder /app/next.config.ts ./
|
|
29
|
+
COPY --from=builder /app/public ./public
|
|
30
|
+
COPY --from=builder /app/.next/standalone ./
|
|
31
|
+
COPY --from=builder /app/.next/static ./.next/static
|
|
32
|
+
|
|
33
|
+
# Expose the port the app runs on
|
|
34
|
+
EXPOSE 3000
|
|
35
|
+
|
|
36
|
+
# Command to run the application
|
|
37
|
+
CMD ["node", "server.js"]
|
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# 🌟 Next-MDX-Blog-Starter
|
|
2
|
+
## 📖 Introduction
|
|
3
|
+
This project is inspired by the elegant design and functionality of **Loveable**. It leverages the **Claude Sonnet 4** model for development, ensuring a robust and efficient coding experience.
|
|
4
|
+
|
|
5
|
+
The goal of this repository is to serve as a comprehensive starter kit for working with static and dynamic content using MDX, React, and Next.js (more specifically the App Router).
|
|
6
|
+
|
|
7
|
+
## ⚙️ Project Setup
|
|
8
|
+
|
|
9
|
+
### Prerequisites
|
|
10
|
+
- **Node.js**: Ensure you have Node.js installed (version 15.x or later).
|
|
11
|
+
- **NPM**: Node Package Manager comes with Node.js.
|
|
12
|
+
|
|
13
|
+
### Installation
|
|
14
|
+
1. Clone the repository:
|
|
15
|
+
```bash
|
|
16
|
+
git clone https://github.com/CodingAbdullah/Next-MDX-Blog-Starter.git
|
|
17
|
+
cd mdx-medium-blog-post-provider
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
2. Install dependencies:
|
|
21
|
+
```bash
|
|
22
|
+
npm install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
3. Start the development server:
|
|
26
|
+
```bash
|
|
27
|
+
npm run dev
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If you want, you can also run this project using the following command:
|
|
31
|
+
```bash
|
|
32
|
+
npx create-next-blog-app .
|
|
33
|
+
```
|
|
34
|
+
This will instantly create and setup the starter kit project for you to work on.
|
|
35
|
+
|
|
36
|
+
## 🛠️ Tools
|
|
37
|
+
### AI Tools
|
|
38
|
+
- **Cursor**: An AI-powered coding assistant that enhances productivity and code quality.
|
|
39
|
+
- **Loveable**: A design inspiration that emphasizes user-friendly interfaces and experiences.
|
|
40
|
+
- **Claude Sonnet 4**: A model that aids in development, providing intelligent suggestions and optimizations.
|
|
41
|
+
|
|
42
|
+
### Common NPM Libraries
|
|
43
|
+
- **mdx**: A markdown format that allows you to write JSX in your markdown files, enabling rich content.
|
|
44
|
+
- **Supabase**: An open-source Firebase alternative that provides a backend as a service, including database and authentication.
|
|
45
|
+
- **shadcn/ui**: A collection of UI components designed for building modern web applications.
|
|
46
|
+
- **lucide-react**: A set of customizable icons for React applications, enhancing visual appeal.
|
|
47
|
+
- **gray-matter**: A library for parsing front matter from markdown files, useful for managing metadata.
|
|
48
|
+
- **react**: A JavaScript library for building user interfaces, allowing for the creation of reusable UI components.
|
|
49
|
+
- **react-syntax-highlighter**: A library for syntax highlighting in React applications, making code snippets more readable.
|
|
50
|
+
- **tsx**: Run TypeScript code without worrying about configuration! Run the `article-manager.ts` for manually working with published articles.
|
|
51
|
+
|
|
52
|
+
## 🌐 Static/Dynamic Rendering with MDX
|
|
53
|
+
This project utilizes MDX for both static and dynamic rendering of blog posts. The two MDX files included in the project serve as examples of how to structure your content.
|
|
54
|
+
|
|
55
|
+
- **Static MDX**: Pre-rendered at build time (<b>route</b>: `/app/sample-blog-post-page`, <b>article content</b>: `/markdown/ArticleContent.mdx`).
|
|
56
|
+
- **Dynamic MDX**: Rendered on the server at request time (<b>route</b>: `/app/dynamic`, <b>article content</b>: `/markdown/DynamicArticleContent.mdx`).
|
|
57
|
+
|
|
58
|
+
### Article Metadata
|
|
59
|
+
`.mdx` files can contain useful information that can be thought of as metadata for the file itself. This is called frontmatter and it is stored at the top of the file using this `---` opening and closing syntax.
|
|
60
|
+
|
|
61
|
+
A helpful package known as `gray-matter` is used to format and separate frontmatter from the article content which is useful when fetching and rendering dynamic MDX content.
|
|
62
|
+
|
|
63
|
+
Dynamic MDX content is rendered with the help of the `next-mdx-remote` package and utilizing the `MDXRemote` custom component.
|
|
64
|
+
|
|
65
|
+
## 🖼️ MDX Components File
|
|
66
|
+
The `mdx-components.tsx` file located in the root, integrates styling for built-in HTML elements as well as optimizing built-in elements such as `<a>` and `<img>` using the built-in components provided by Next.js (`<Image>` and `<Link>`).
|
|
67
|
+
|
|
68
|
+
For more details on what this file is and how it is utilized in a Next.js application, you can refer to the official docs here.
|
|
69
|
+
|
|
70
|
+
## 🧩 Custom React Components
|
|
71
|
+
Custom React components are created for enhanced functionality when working with MDX. The following are located in the `customMDXComponents` directory inside the `components` directory of the project.
|
|
72
|
+
|
|
73
|
+
### Syntax Highlighting
|
|
74
|
+
The project includes a custom `CodeBlock` component for syntax highlighting code blocks using the `react-syntax-highlighter` package.
|
|
75
|
+
|
|
76
|
+
Default theme is set to the `vscDarkPlus` theme. Feel free to modify the theme and even add your own syntax highlighting library if you so choose.
|
|
77
|
+
|
|
78
|
+
You can read more about the library used in this project here.
|
|
79
|
+
|
|
80
|
+
### GitHub Gists
|
|
81
|
+
For safety and ease of use, GitHub Gists can be integrated into MDX files to manage code snippets with its own custom component. Note that only GitHub Gists that are publicly available are supported. You can modify the component to integrate private Gists.
|
|
82
|
+
|
|
83
|
+
This custom component also utilizes the `react-syntax-highlighter` package for syntax highlighting the publicly accessible GitHub gists.
|
|
84
|
+
|
|
85
|
+
### MDX Images
|
|
86
|
+
The project comes with its own `MDXImage` component that utilizes the Next.js built-in `Image` component as well as the built-in `figure` and `figcaption` elements to integrate imaging and captions seamlessly.
|
|
87
|
+
|
|
88
|
+
## 🧩 Constants, Functions, & Types
|
|
89
|
+
In this project, you will find custom constants, functions, and types in the `/src/utils/` directory. Certain constants serve as placeholders in this demo application. While functions and data types are integral to the function of this web application, feel free to check them out.
|
|
90
|
+
|
|
91
|
+
## 🗄️ Supabase Database Setup
|
|
92
|
+
The project uses Supabase for database management. The `SupabaseClient` module is configured to interact with your Supabase instance. The instance is located in `/src/utils/functions/supabase_client`.
|
|
93
|
+
|
|
94
|
+
**Ensure that the policies of the Supabase database enable you to perform the desired CRUD actions against your tables. You can modify these in the Supabase console.**
|
|
95
|
+
|
|
96
|
+
### Environment Variables
|
|
97
|
+
Create a `.env` file in the root directory and add your Supabase secrets:
|
|
98
|
+
|
|
99
|
+
``
|
|
100
|
+
SUPABASE_URL=your_supabase_url
|
|
101
|
+
``
|
|
102
|
+
|
|
103
|
+
``
|
|
104
|
+
SUPABASE_ANON_KEY=your_supabase_anon_key
|
|
105
|
+
``
|
|
106
|
+
|
|
107
|
+
## 🌩️ AWS
|
|
108
|
+
This application utilizes the AWS S3 service for the storage of images. You can find the external URL used to access these objects in the `next.config.ts` file. Feel free to use another service or modify the URL to point to a S3 bucket of your own.
|
|
109
|
+
|
|
110
|
+
Images are used in the `.mdx` files and utilized via the custom `MDXImage` component covered earlier.
|
|
111
|
+
|
|
112
|
+
## 🐳 Docker
|
|
113
|
+
This application can be containerized using Docker.
|
|
114
|
+
|
|
115
|
+
To build an image, utilize the Dockerfile located in the root location of the repository and run the following commands to run this web application in a standalone container (passing in the Supabase credentials as well):
|
|
116
|
+
|
|
117
|
+
``
|
|
118
|
+
docker build -t mdx-medium-blog .
|
|
119
|
+
``
|
|
120
|
+
|
|
121
|
+
``
|
|
122
|
+
docker run -e SUPABASE_URL=your_supabase_url \
|
|
123
|
+
-e SUPABASE_ANON_KEY=your_supabase_anon_key \
|
|
124
|
+
-p 3000:3000 mdx-medium-blog
|
|
125
|
+
``
|
|
126
|
+
|
|
127
|
+
## 🔄 CRUD Operations and Supabase Actions
|
|
128
|
+
Implementation of the CRUD operation functions is stored in the `/src/utils/functions/crud` directory. This includes functions for creating, reading, updating, and deleting articles in the Supabase database.
|
|
129
|
+
|
|
130
|
+
The `article-manager.ts` file utilizes these CRUD functions to successfully perform the desired actions.
|
|
131
|
+
|
|
132
|
+
## 📜 Scripts for Setting Up Project
|
|
133
|
+
The `/scripts` folder contains various scripts to help set up the project and database.
|
|
134
|
+
|
|
135
|
+
### Setup Scripts
|
|
136
|
+
- **SQL Scripts**: DDL and DML statements for initializing and populating the database.
|
|
137
|
+
- **Powershell Script**: Script for setting up project on Windows.
|
|
138
|
+
- **Bash Shell Script**: Script for setting up project on Linux, Mac, etc.
|
|
139
|
+
|
|
140
|
+
This project directly integrates Supabase in the front-end using React Server components which removes the need for custom back-end APIs.
|
|
141
|
+
|
|
142
|
+
## 🛠️ Compiling TypeScript and Testing Locally
|
|
143
|
+
The `article-manager.ts` file (located in `/scripts/action-script`) utilizes the `tsx` package to perform the different actions (CRUD operations) on articles stored locally.
|
|
144
|
+
|
|
145
|
+
You can use this handy script for testing MDX and article functionality locally.
|
|
146
|
+
|
|
147
|
+
The following codeblock highlights the different command prompts that can be used with this file:
|
|
148
|
+
|
|
149
|
+
- Insert an Article into Supabase
|
|
150
|
+
npx tsx article-manager.ts insert article-slug
|
|
151
|
+
|
|
152
|
+
- Delete an Article from Supabase
|
|
153
|
+
npx tsx article-manager.ts delete article-slug
|
|
154
|
+
|
|
155
|
+
- Update an Article in Supabase
|
|
156
|
+
npx tsx article-manager.ts update article-slug new-article-file
|
|
157
|
+
|
|
158
|
+
- Fetch a Single Article from Supabase
|
|
159
|
+
npx tsx article-manager.ts fetch article-slug
|
|
160
|
+
|
|
161
|
+
- Fetch all Articles from Supabase
|
|
162
|
+
npx tsx article-manager.ts fetchAll
|
|
163
|
+
|
|
164
|
+
The article slug is the name of the markdown file located in the `/src/markdown` directory minus the extension, `.mdx`.
|
|
165
|
+
|
|
166
|
+
The update statement takes in an additional parameter which is also the same thing (file name minus the `.mdx` extension located in the `/src/markdown` directory).
|
|
167
|
+
|
|
168
|
+
## 📊 Analytics
|
|
169
|
+
Integrated in this setup project is Vercel Analytics (`@vercel/analytics`) to track user interactions and performance metrics of the blog.
|
|
170
|
+
|
|
171
|
+
## ⚙️ Next.js Configuration
|
|
172
|
+
The `next.config.ts` file is set up for working with AWS S3 and includes MDX extensions for enhanced functionality. Feel free to modify and add your own custom links to access storage and setting up other configurations.
|
|
173
|
+
|
|
174
|
+
## 🔗 Useful Links
|
|
175
|
+
- [Next.js Documentation](https://nextjs.org/docs)
|
|
176
|
+
- [MDX Documentation](https://mdxjs.com/docs/)
|
|
177
|
+
- [Lee Robinson's Next.js + MDX Tutorial Video](https://www.youtube.com/watch?v=34bRv6cQezo&ab_channel=leerob)
|
|
178
|
+
- [MDX Playground](https://mdxjs.com/playground/)
|
|
179
|
+
- [React Documentation](https://reactjs.org/docs/getting-started.html)
|
|
180
|
+
- [Supabase Documentation](https://supabase.com/docs)
|
|
181
|
+
- [Docker Documentation](https://docs.docker.com/)
|
|
182
|
+
- [React Syntax Highlighter Package](https://github.com/react-syntax-highlighter/react-syntax-highlighter)
|
package/cli.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const execa = require('execa');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
|
|
8
|
+
// Script for setting up CLI command for setting up project
|
|
9
|
+
async function setup() {
|
|
10
|
+
// Check if we are inside a project folder
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
const hasPackageJson = fs.existsSync(path.join(cwd, 'package.json'));
|
|
13
|
+
|
|
14
|
+
if (hasPackageJson) {
|
|
15
|
+
console.log('You already have a project setup here.');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Ask user for confirmation to set up
|
|
20
|
+
const answers = await inquirer.prompt([
|
|
21
|
+
{
|
|
22
|
+
type: 'confirm',
|
|
23
|
+
name: 'install',
|
|
24
|
+
message: 'Do you want to set up a Next.js blog app here?',
|
|
25
|
+
default: true,
|
|
26
|
+
},
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
if (!answers.install) {
|
|
30
|
+
console.log('Aborted the installation.');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log('Setting up the project...');
|
|
35
|
+
|
|
36
|
+
// Initialize npm and install dependencies
|
|
37
|
+
try {
|
|
38
|
+
// Initialize package.json
|
|
39
|
+
await execa('npm', ['init', '-y']);
|
|
40
|
+
|
|
41
|
+
// Install dependencies
|
|
42
|
+
await execa('npm', ['install'], {
|
|
43
|
+
stdio: 'inherit',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
console.log('Next.js Blog setup completed!');
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error('Error during setup:', error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
setup();
|
package/components.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/app/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/components",
|
|
15
|
+
"utils": "@/lib/utils",
|
|
16
|
+
"ui": "@/components/ui",
|
|
17
|
+
"lib": "@/lib",
|
|
18
|
+
"hooks": "@/hooks"
|
|
19
|
+
},
|
|
20
|
+
"iconLibrary": "lucide"
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { dirname } from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { FlatCompat } from "@eslint/eslintrc";
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const compat = new FlatCompat({
|
|
9
|
+
baseDirectory: __dirname,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const eslintConfig = [
|
|
13
|
+
...compat.extends("next/core-web-vitals", "next/typescript"),
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export default eslintConfig;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import type { MDXComponents } from 'mdx/types';
|
|
2
|
+
import Image, { ImageProps } from 'next/image';
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
import CodeBlock from '@/components/customMDXComponents/CodeBlock';
|
|
6
|
+
import GitHubGist from '@/components/customMDXComponents/GitHubGist';
|
|
7
|
+
import MDXImage from '@/components/customMDXComponents/MDXImage';
|
|
8
|
+
// Custom MDX components for writing Medium article style blog posts
|
|
9
|
+
// Optimize Links and Images using Image and Link built-in components replacing img, a tags
|
|
10
|
+
// Code blocks, a, b, u, i, img, ul, ol, blockquote, h1, h2, h3, h4, h5, h6, custom component for handling GitHub Gists
|
|
11
|
+
// React-Syntax-Highlighting for Code Blocks (GitHub Gists) (integrate into MDX components file)
|
|
12
|
+
// Supabase containing Article Metadata (Name, Subtitle, Tags, Date, Slug, Description, etc.)
|
|
13
|
+
// *Check to see if Supabase can be used to store article content*
|
|
14
|
+
// Map markdown slugs to dynamic file path routing, loading static files
|
|
15
|
+
// Supabase for storing article metadata (article content?)
|
|
16
|
+
// AWS S3 for article images, thumbnails, etc.
|
|
17
|
+
// Gray Matter for MDX content metadata
|
|
18
|
+
// Dockerize the application
|
|
19
|
+
// Separate branch for SvelteKit version
|
|
20
|
+
// npx command script for downloading starting template
|
|
21
|
+
// Powershell/Shell script for creating the npx command script
|
|
22
|
+
// NPM package publishing as well
|
|
23
|
+
/*
|
|
24
|
+
// Example of Metadata for a MDX file
|
|
25
|
+
---
|
|
26
|
+
title: "Understanding SEO in MDX"
|
|
27
|
+
description: "A guide on how to optimize MDX pages for search engines"
|
|
28
|
+
author: "John Doe"
|
|
29
|
+
date: "2025-05-28"
|
|
30
|
+
tags: ["SEO", "MDX", "React"]
|
|
31
|
+
slug: "/seo-in-mdx"
|
|
32
|
+
---
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// MDX Components mapper for both built-in and custom components
|
|
36
|
+
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
|
37
|
+
return {
|
|
38
|
+
h1: ({ children, ...props }) => (
|
|
39
|
+
<h1 {...props} className={cn("text-3xl font-bold text-green-300 matrix-glow mt-8 mb-4", props.className)}>
|
|
40
|
+
{children}
|
|
41
|
+
</h1>
|
|
42
|
+
),
|
|
43
|
+
h2: ({ children, ...props }) => (
|
|
44
|
+
<h2 {...props} className={cn("text-2xl font-bold text-green-300 matrix-glow mt-8 mb-4 border-b border-green-500/20 pb-2", props.className)}>
|
|
45
|
+
{children}
|
|
46
|
+
</h2>
|
|
47
|
+
),
|
|
48
|
+
h3: ({ children, ...props }) => (
|
|
49
|
+
<h3 {...props} className={cn("text-xl font-bold text-green-300 matrix-glow mt-6 mb-3", props.className)}>
|
|
50
|
+
{children}
|
|
51
|
+
</h3>
|
|
52
|
+
),
|
|
53
|
+
h4: ({ children, ...props }) => (
|
|
54
|
+
<h4 {...props} className={cn("text-lg font-bold text-green-300 matrix-glow mt-6 mb-2", props.className)}>
|
|
55
|
+
{children}
|
|
56
|
+
</h4>
|
|
57
|
+
),
|
|
58
|
+
h5: ({ children, ...props }) => (
|
|
59
|
+
<h5 {...props} className={cn("text-base font-bold text-green-300 matrix-glow mt-4 mb-2", props.className)}>
|
|
60
|
+
{children}
|
|
61
|
+
</h5>
|
|
62
|
+
),
|
|
63
|
+
h6: ({ children, ...props }) => (
|
|
64
|
+
<h6 {...props} className={cn("text-base font-bold text-green-300 mt-4 mb-2", props.className)}>
|
|
65
|
+
{children}
|
|
66
|
+
</h6>
|
|
67
|
+
),
|
|
68
|
+
p: ({ children, ...props }) => {
|
|
69
|
+
// Check if the children is a code block
|
|
70
|
+
if (typeof children === 'string' && children.startsWith('```')) {
|
|
71
|
+
return <>{children}</>;
|
|
72
|
+
}
|
|
73
|
+
return (
|
|
74
|
+
<p {...props} className={cn("mb-4 leading-relaxed text-green-200/90", props.className)}>
|
|
75
|
+
{children}
|
|
76
|
+
</p>
|
|
77
|
+
);
|
|
78
|
+
},
|
|
79
|
+
a: ({ children, href, ...props }) => {
|
|
80
|
+
if (href?.startsWith('/')) {
|
|
81
|
+
return (
|
|
82
|
+
<Link href={href} className={cn("text-green-400 hover:text-green-300 underline", props.className)} {...props}>
|
|
83
|
+
{children}
|
|
84
|
+
</Link>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
else if (href?.startsWith('https') || href?.startsWith('http')) {
|
|
88
|
+
return (
|
|
89
|
+
<a href={href} className={cn("text-green-400 hover:text-green-300 underline", props.className)} {...props}>
|
|
90
|
+
{children}
|
|
91
|
+
</a>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
return (
|
|
96
|
+
<Link
|
|
97
|
+
{...props}
|
|
98
|
+
href={href || ''}
|
|
99
|
+
className={cn("text-green-400 hover:text-green-300 underline", props.className)}>
|
|
100
|
+
{children}
|
|
101
|
+
</Link>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
ul: ({ children, ...props }) => (
|
|
106
|
+
<ul {...props} className={cn("list-disc pl-6 mb-4 space-y-1", props.className)}>
|
|
107
|
+
{children}
|
|
108
|
+
</ul>
|
|
109
|
+
),
|
|
110
|
+
ol: ({ children, ...props }) => (
|
|
111
|
+
<ol {...props} className={cn("list-decimal pl-6 mb-4 space-y-1", props.className)}>
|
|
112
|
+
{children}
|
|
113
|
+
</ol>
|
|
114
|
+
),
|
|
115
|
+
li: ({ children, ...props }) => (
|
|
116
|
+
<li {...props} className={cn("mb-1 text-green-200/90", props.className)}>
|
|
117
|
+
{children}
|
|
118
|
+
</li>
|
|
119
|
+
),
|
|
120
|
+
blockquote: ({ children, ...props }) => (
|
|
121
|
+
<blockquote
|
|
122
|
+
{...props}
|
|
123
|
+
className={cn("border-l-4 border-green-500/50 pl-4 py-1 mb-4 text-green-200/80 italic", props.className)}
|
|
124
|
+
>
|
|
125
|
+
{children}
|
|
126
|
+
</blockquote>
|
|
127
|
+
),
|
|
128
|
+
img: (props) => (
|
|
129
|
+
<Image
|
|
130
|
+
width={50}
|
|
131
|
+
height={50}
|
|
132
|
+
sizes="100vw"
|
|
133
|
+
style={{ width: '50%', height: 'auto' }}
|
|
134
|
+
{...(props as ImageProps)}
|
|
135
|
+
/>
|
|
136
|
+
),
|
|
137
|
+
pre: ({ children, ...props }) => {
|
|
138
|
+
const codeChild =
|
|
139
|
+
typeof children === 'object' &&
|
|
140
|
+
children &&
|
|
141
|
+
'props' in children &&
|
|
142
|
+
children.props;
|
|
143
|
+
|
|
144
|
+
if (codeChild) {
|
|
145
|
+
const { className, children: codeContent } = codeChild;
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<CodeBlock className={className}>
|
|
149
|
+
{codeContent}
|
|
150
|
+
</CodeBlock>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<pre {...props} className={cn("not-prose my-6", props.className)}>
|
|
156
|
+
{children}
|
|
157
|
+
</pre>
|
|
158
|
+
);
|
|
159
|
+
},
|
|
160
|
+
strong: ({ children, ...props }) => (
|
|
161
|
+
<strong {...props} className="font-bold">
|
|
162
|
+
{children}
|
|
163
|
+
</strong>
|
|
164
|
+
),
|
|
165
|
+
em: ({ children, ...props }) => (
|
|
166
|
+
<em {...props} className="italic">
|
|
167
|
+
{children}
|
|
168
|
+
</em>
|
|
169
|
+
),
|
|
170
|
+
del: ({ children, ...props }) => (
|
|
171
|
+
<del {...props} className="line-through">
|
|
172
|
+
{children}
|
|
173
|
+
</del>
|
|
174
|
+
),
|
|
175
|
+
table: ({ children, ...props }) => (
|
|
176
|
+
<div className="overflow-x-auto mb-6">
|
|
177
|
+
<table {...props} className="min-w-full border-collapse">
|
|
178
|
+
{children}
|
|
179
|
+
</table>
|
|
180
|
+
</div>
|
|
181
|
+
),
|
|
182
|
+
thead: ({ children, ...props }) => (
|
|
183
|
+
<thead {...props} className="bg-green-900/30 border-b border-green-500/30">
|
|
184
|
+
{children}
|
|
185
|
+
</thead>
|
|
186
|
+
),
|
|
187
|
+
th: ({ children, ...props }) => (
|
|
188
|
+
<th {...props} className="px-6 py-3 text-left text-sm font-semibold text-green-300">
|
|
189
|
+
{children}
|
|
190
|
+
</th>
|
|
191
|
+
),
|
|
192
|
+
tr: ({ children, ...props }) => (
|
|
193
|
+
<tr {...props} className="border-b border-green-500/20">
|
|
194
|
+
{children}
|
|
195
|
+
</tr>
|
|
196
|
+
),
|
|
197
|
+
td: ({ children, ...props }) => (
|
|
198
|
+
<td {...props} className="px-6 py-4 text-sm">
|
|
199
|
+
{children}
|
|
200
|
+
</td>
|
|
201
|
+
),
|
|
202
|
+
hr: ({ ...props }) => (
|
|
203
|
+
<hr {...props} className="my-6 border-green-500/20" />
|
|
204
|
+
),
|
|
205
|
+
GitHubGist,
|
|
206
|
+
MDXImage
|
|
207
|
+
}
|
|
208
|
+
};
|
package/next.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import createMDX from '@next/mdx';
|
|
2
|
+
import type { NextConfig } from 'next';
|
|
3
|
+
|
|
4
|
+
// Adding acceptable page extensions to be used in this application
|
|
5
|
+
// Adding external domains to be used in this application
|
|
6
|
+
const nextConfig: NextConfig = {
|
|
7
|
+
// Configure `pageExtensions` to include markdown and MDX files
|
|
8
|
+
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
|
|
9
|
+
images: {
|
|
10
|
+
domains: ["mdx-blog-bucket.s3.us-east-2.amazonaws.com"]
|
|
11
|
+
},
|
|
12
|
+
experimental: {
|
|
13
|
+
mdxRs: true,
|
|
14
|
+
viewTransition: true
|
|
15
|
+
},
|
|
16
|
+
output: 'standalone' as const
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const withMDX = createMDX({});
|
|
20
|
+
|
|
21
|
+
// Wrapping app configurations with the withMDX function
|
|
22
|
+
export default withMDX(nextConfig);
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-next-mdx-blog-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"bin": {
|
|
5
|
+
"create-next-blog-app": "./cli.js"
|
|
6
|
+
},
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "next dev",
|
|
9
|
+
"build": "next build",
|
|
10
|
+
"start": "next start",
|
|
11
|
+
"lint": "next lint"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@mdx-js/loader": "^3.1.0",
|
|
15
|
+
"@mdx-js/react": "^3.1.0",
|
|
16
|
+
"@next/mdx": "^15.3.2",
|
|
17
|
+
"@radix-ui/react-avatar": "^1.1.10",
|
|
18
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
19
|
+
"@supabase/supabase-js": "^2.49.8",
|
|
20
|
+
"@types/mdx": "^2.0.13",
|
|
21
|
+
"@types/react-syntax-highlighter": "^15.5.13",
|
|
22
|
+
"@vercel/analytics": "^1.5.0",
|
|
23
|
+
"class-variance-authority": "^0.7.1",
|
|
24
|
+
"clsx": "^2.1.1",
|
|
25
|
+
"execa": "^9.6.0",
|
|
26
|
+
"gray-matter": "^4.0.3",
|
|
27
|
+
"inquirer": "^12.6.3",
|
|
28
|
+
"lucide-react": "^0.511.0",
|
|
29
|
+
"next": "15.3.2",
|
|
30
|
+
"next-mdx-remote": "^5.0.0",
|
|
31
|
+
"react": "^19.0.0",
|
|
32
|
+
"react-dom": "^19.0.0",
|
|
33
|
+
"react-syntax-highlighter": "^15.6.1",
|
|
34
|
+
"tailwind-merge": "^3.3.0",
|
|
35
|
+
"tsx": "^4.19.4"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@eslint/eslintrc": "^3",
|
|
39
|
+
"@tailwindcss/postcss": "^4",
|
|
40
|
+
"@types/node": "^20.17.58",
|
|
41
|
+
"@types/react": "^19",
|
|
42
|
+
"@types/react-dom": "^19",
|
|
43
|
+
"eslint": "^9",
|
|
44
|
+
"eslint-config-next": "15.3.2",
|
|
45
|
+
"tailwindcss": "^4",
|
|
46
|
+
"tw-animate-css": "^1.3.2",
|
|
47
|
+
"typescript": "^5.8.3"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/public/file.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
package/public/globe.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
package/public/next.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|