Die zu verwendenden Pakete:
Das Repository für dieses Tutorial:
https://github.com/tszhong0411/nextjs-mdx-blog
Online-Demo
Zuerst erstellen wir das Next.js-Projekt mit folgendem Befehl:
yarn create next-app nextjs-mdx-blog
Erstellen Sie als Nächstes die folgenden Dateien:
components/layout.jsx
– Umschließt alle Komponenten in einem Container (optional, dient nur dem Styling)
data/blog/*.mdx
– Blogartikel
lib/format-date.js
– Formatiert das Datum als MM DD, YYYY
pages/blog/[slug].jsx
– Artikelseite unter Verwendung von dynamischen Routen
Zuerst definieren wir const root
– das Stammverzeichnis, und die Methode process.cwd()
gibt das aktuelle Arbeitsverzeichnis des Node.js-Prozesses zurück.
const root = process. cwd ()
Eine weitere Variable, POSTS_PATH
, definiert den Pfad, in dem die Artikeldateien gespeichert sind.
import path from 'path'
const POSTS_PATH = path. join (root, 'data' , 'blog' )
// Ausgabe: A:/nextjs-mdx-blog/data/blog
Verwenden Sie anschließend fs
, um den Inhalt dieses Verzeichnisses zu lesen – also alle Dateinamen unter data/blog
.
import fs from 'fs'
export const allSlugs = fs. readdirSync ( POSTS_PATH )
// Ausgabe: ['markdown.mdx', 'nextjs.mdx', 'react.mdx']
Schreiben Sie nun eine Funktion, die die Dateiendung entfernt, welche später verwendet wird.
export const formatSlug = ( slug ) => slug. replace ( / \. mdx $ / , '' )
/**
* Beispiel: formatSlug('markdown.mdx')
* Ausgabe: 'markdown'
*/
Der nächste Schritt besteht darin, den Artikelinhalt anhand des Slugs zu erhalten.
export const getPostBySlug = async ( slug ) => {
const postFilePath = path. join ( POSTS_PATH , `${ slug }.mdx` )
// Ausgabe: A:/nextjs-mdx-blog/data/blog/slug.mdx
const source = fs. readFileSync (postFilePath)
// Gibt den Dateiinhalt zurück
const { content , data } = matter (source)
/*
* Beispiel:
* ---
* title: Hello
* slug: home
* ---
* <h1>Hello world!</h1>
*
* Ausgabe:
* {
* content: '<h1>Hello world!</h1>',
* data: {
* title: 'Hello',
* slug: 'home'
* }
* }
*/
const mdxSource = await serialize (content)
// Übergibt den Inhalt an serialize (next-mdx-remote) zur Verarbeitung
const frontMatter = {
... data,
slug
}
// Fügt den Slug zur Frontmatter hinzu, was später verwendet wird
return {
source: mdxSource,
frontMatter
}
}
Anschließend können Sie alle Artikel abrufen, die auf der Startseite angezeigt werden sollen.
export const getAllPosts = () => {
const frontMatter = []
allSlugs. forEach (( slug ) => {
const source = fs. readFileSync (path. join ( POSTS_PATH , slug), 'utf-8' )
const { data } = matter (source)
frontMatter. push ({
... data,
slug: formatSlug (slug),
date: new Date (data.date). toISOString ()
})
})
return frontMatter. sort (( a , b ) => dateSortDesc (a.date, b.date))
}
// Nach Datum in absteigender Reihenfolge sortiert
const dateSortDesc = ( a , b ) => {
if (a > b) return - 1
if (a < b) return 1
return 0
}
export const formatDate = ( date ) =>
new Date (date). toLocaleDateString ( 'en' , {
year: 'numeric' ,
month: 'long' ,
day: 'numeric'
})
/*
* formatDate('2022-08-21T00:00:00Z')
* Ausgabe: 'August 21, 2022'
*/
import { formatDate } from '../lib/format-date'
import { getAllPosts } from '../lib/mdx'
import Link from 'next/link'
export default function Home ({ posts }) {
return (
<>
< h1 className = 'mb-8 text-6xl font-bold' >Blog</ h1 >
< hr className = 'my-8' />
< ul className = 'flex flex-col gap-3' >
{ posts. map (({ slug , title , summary , date }) => (
< li key ={ slug } >
< Link href ={ `/blog/${ slug }` } >
< a className = 'block rounded-lg border border-solid border-gray-300 p-6 shadow-md' >
< div className = 'flex justify-between' >
< h2 > { title } </ h2 >
< time dateTime ={ date } > { formatDate (date) } </ time >
</ div >
< p className = 'mt-4' > { summary } </ p >
</ a >
</ Link >
</ li >
)) }
</ ul >
</>
)
}
// Verwendet getStaticProps, um alle Artikel abzurufen
export const getStaticProps = async () => {
const posts = getAllPosts ()
return {
props: {
posts
}
}
}
import { formatDate } from '../../lib/format-date'
import { allSlugs, formatSlug, getPostBySlug } from '../../lib/mdx'
import { MDXRemote } from 'next-mdx-remote'
export default function Blog ({ post }) {
const { title , date } = post.frontMatter
return (
< div >
< h1 className = 'mb-2 text-6xl font-bold' > { title } </ h1 >
< time dateTime ={ date } className = 'text-lg font-medium' >
{ formatDate (date) }
</ time >
< hr className = 'my-8' />
< article className = 'prose max-w-none' >
< MDXRemote {... post.source } />
</ article >
</ div >
)
}
export const getStaticProps = async ({ params }) => {
const post = await getPostBySlug (params.slug)
return {
props: {
post
}
}
}
export const getStaticPaths = async () => {
const paths = allSlugs. map (( slug ) => ({
params: {
slug: formatSlug (slug)
}
}))
/*
* Ausgabe von paths:
* [
* { params: { slug: 'markdown' } },
* { params: { slug: 'nextjs' } },
* { params: { slug: 'react' } }
* ]
*/
return {
paths,
fallback: false
}
}
Auf diese Weise ist ein einfacher Blog fertiggestellt.