创建一个 Next.js Markdown 博客。图片

2025-06-09

构建 Next.js Markdown 博客。

图像

注意:这是一个高级主题,所以我假设您已经熟悉 React、JavaScript 和 Web 开发的基础知识。

Next.Js

Nextjs 是一个React框架。它是最受欢迎的框架,因为它易于使用、非常灵活,并且拥有强大的基于文件的路由系统。它提供开箱即用的服务器端渲染功能。

让我们深入探讨

如果你不想一起写代码,只想看代码,请查看源代码

我不得不为我的个人作品集网站创建博客。网上虽然有一些文章,但我找不到任何简单的解决方案。所以我决定写一篇关于这个问题的简单文章。让我们开始吧

要创建 nextjs 应用程序,请在终端中运行以下命令

npm init next-app
# or
yarn create next-app
Enter fullscreen mode Exit fullscreen mode

您可以使用npmyarn包管理器,但我将使用yarn

给你的项目命名。包管理器将安装所有必要的包。

运行此命令

cd YOUR_PROJECT_NAME

启动项目

yarn dev

您的项目应该在端口3000上线。您应该看到类似这样的内容

替代文本

太棒了pages/index.js删除所有内容并粘贴以下代码

import React from "react";

const Index = () => {
  return <h1>My First Blog ✍ </h1>;
};

export default Index;
Enter fullscreen mode Exit fullscreen mode

在文件夹根目录创建一个文件config.json,并提供网站标题和描述。(此步骤用于 SEO 目的)。

{
  "title": "Nextjs Blog Site",
  "description": "A Simple Markdown Blog build with Nextjs."
}
Enter fullscreen mode Exit fullscreen mode

在根目录中创建一个名为 的文件夹。我们的文件将存放content在这里。.md

现在你的文件夹结构应该如下所示

替代文本

components目录将包含我们的博客逻辑

内容目录将包含我们的 markdown 文件

pages目录包含我们的页面(路线)

用于提供静态文件(资产)的公共目录

让我们打开pages/index.js并导入网站标题和描述config.json

import React from "react";

const Index = (props) => {
  console.log("Index -> props", props);

  return <h1>My First Blog ✍ </h1>;
};

export default Index;


export async function getStaticProps() {
  const siteData = await import(`../config.json`);

  return {
    props: {
      title: siteData.default.title,
      description: siteData.default.description,
    },
  };
}

Enter fullscreen mode Exit fullscreen mode

保存此页面后,您应该在浏览器的控制台中看到类似这样的内容。

Index -> props {title: "Nextjs Blog Site", description: "A Simple Markdown Blog build with Nextjs."}

好吧,刚才发生了什么?我们来分析一下

getStaticProps getStaticProps是 Nextjs 函数,我们可以从中调用它page。它会将 props 返回给我们的组件。就像props我们index

我们稍后将使用此方法来获取我们的帖子。

内容将在构建时生成。如果您不明白这是什么意思,不用担心,只需记住,内容在构建前即可使用,我们不会每次用户访问我们的网站时都获取帖子。很酷吧?

我们正在导入我们的文件并返回组件config.json标题和描述propsindex

Next.js 还为我们提供了Head可以将元素附加到head页面的组件,例如网站标题、meta标签links等。

import Head from 'next/head'

 <Head>
    <title>My page title</title>
    <meta name="viewport" content="initial-scale=1.0, width=device-width" />
  </Head>
Enter fullscreen mode Exit fullscreen mode

让我们将其添加到我们的Index页面

import React from "react";
import Head from "next/head";

const Index = (props) => {
  return (
    <>
      <Head>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charSet="utf-8" />
        <meta name="Description" content={props.description}></meta>
        <title>{props.title}</title>
      </Head>
      <h1>My First Blog ✍ </h1>;
    </>
  );
};

export default Index;

export async function getStaticProps() {
  const siteData = await import(`../config.json`);

  return {
    props: {
      title: siteData.default.title,
      description: siteData.default.description,
    },
  };
}

Enter fullscreen mode Exit fullscreen mode

添加后Head,查看浏览器的标签页,你看到了什么?网站标题已更新。

替代文本

理想情况下,您可能希望将其放入布局组件,但在我们的例子中,我认为这很好。

现在回到我们的博客。我们需要在项目中添加一些包。运行以下命令
yarn add react-markdown gray-matter raw-loader

或者

npm install react-markdown gray-matter raw-loader

react-markdown将帮助我们解析和渲染 markdown 文件

gray-matter将解析我们博客的前言。(文件顶部之间的部分---

我们需要这些元数据,包括title您可以在此处添加任何您喜欢的内容(例如英雄图片的 URL)。datadescriptionslug

raw-loader将帮助我们导入 markdown 文件。

安装完成后,我们需要进行一些 web pack 配置。next.config.js在根目录中创建一个文件

并粘贴以下代码。

module.exports = {
  webpack: function(config) {
    config.module.rules.push({
      test: /\.md$/,
      use: 'raw-loader',
    })
    return config
  }
}
Enter fullscreen mode Exit fullscreen mode

注意:创建此文件后,您必须重新启动开发服务器。

content目录中创建两个 markdown 文件

content/blog-one.md

---

slug: blog-one
title: My First Blog
description: This Description Of My First Blog.
date: 25-September-2020
---

# h1

## h2

### h3

Normal text

Enter fullscreen mode Exit fullscreen mode

content/blog-two.md

---
slug: blog-two
title: My Second Blog
description: This Description Of My Second Blog.
date: 25-September-2020
---

# h1

## h2

### h3

Normal text

Enter fullscreen mode Exit fullscreen mode

首先,我们将呈现带有标题和描述的博客列表。

在我们的index.js替换getStaticProps函数中

export async function getStaticProps() {
  const siteData = await import(`../config.json`);
  const fs = require("fs");

  const files = fs.readdirSync(`${process.cwd()}/content`, "utf-8");

  const blogs = files.filter((fn) => fn.endsWith(".md"));

  const data = blogs.map((blog) => {
    const path = `${process.cwd()}/content/${blog}`;
    const rawContent = fs.readFileSync(path, {
      encoding: "utf-8",
    });

    return rawContent;
  });

  return {
    props: {
      data: data,
      title: siteData.default.title,
      description: siteData.default.description,
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

fsnodejs帮助我们读写文件的模块。我们将用它fs.readdirSync来读取文件。

process.cwd()这将返回 Next.js 的执行目录。我们需要从当前目录(根目录)读取/content所有文件,并将它们存储在变量中。files

endsWith endsWith是一个 JavaScript 字符串方法,它确定字符串是否以指定字符串的字符结尾,并根据需要返回true或。false

我们将绘制博客地图并path获取rawContent

现在我们的index组件将接收dataprop。

import React from "react";
import Head from "next/head";
import matter from "gray-matter";
import Link from "next/link";

const Index = ({ data, title, description }) => {
  const RealData = data.map((blog) => matter(blog));
  const ListItems = RealData.map((listItem) => listItem.data);

  return (
    <>
      <Head>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charSet="utf-8" />
        <meta name="Description" content={description}></meta>
        <title>{title}</title>
      </Head>
      <h1>My First Blog ✍ </h1>;
      <div>
        <ul>
          {ListItems.map((blog, i) => (
            <li key={i}>
              <Link href={`/${blog.slug}`}>
                <a>{blog.title}</a>
              </Link>
                <p>{blog.description}</p>
            </li>
          ))}
        </ul>
      </div>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

我们正在映射data和格式化每个博客gray-matter

此时,你应该看到类似这样的内容

替代文本

如果您点击“我的第一个博客”,它将带您到/blog-one您为博客命名的任何位置

动态路线

我们可能有五十个不同的博客。我们不想为每个博客都创建一个页面。如果我们在 pages 目录中创建一个文件,blog我们就可以导航到localhost:3000/blog。但是,如果像这样在 blog(文件名)两边添加方括号,[blog].js我们就有了一个动态路由。

路线最终将到达localhost:3000/:blog

[blog].js在 pages 目录中创建新页面

import react from "react";

const Blog = () => {
  return <h1>Blog</h1>;
};

export default Blog;

Enter fullscreen mode Exit fullscreen mode

content现在让我们从目录中获取文件

Blog.getInitialProps = async (context) => {
  const { blog } = context.query;

  const content = await import(`../content/${blog}.md`);
  const data = matter(content.default);

  return { ...data };
};

Enter fullscreen mode Exit fullscreen mode

您应该组件中content提供dataBlog

import react from "react";
import matter from "gray-matter";
import ReactMarkdown from "react-markdown";

const Blog = ({ content, data }) => {
  const frontmatter = data;

  return (
    <>
      <h1>{frontmatter.title}</h1>
      <h3>{frontmatter.description}</h3>
      <ReactMarkdown escapeHtml={true} source={content} />
    </>
  );
};

export default Blog;

Blog.getInitialProps = async (context) => {
  const { blog } = context.query;
  // Import our .md file using the `slug` from the URL
  const content = await import(`../content/${blog}.md`);
  const data = matter(content.default);

  return { ...data };
};

Enter fullscreen mode Exit fullscreen mode

天哪!它起作用了。

那么代码呢

对于代码格式化,我们将使用react-syntax-highlighter

yarn add react-syntax-highlighter

现在创建一个代码块[blog].js并将其传递给ReactMarkdown

import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";

const CodeBlock = ({ language, value }) => {
  return (
    <SyntaxHighlighter showLineNumbers={true} language={language}>
      {value}
    </SyntaxHighlighter>
  );
};
Enter fullscreen mode Exit fullscreen mode

现在你[blog].js应该看起来像这样

import react from "react";
import matter from "gray-matter";
import ReactMarkdown from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";

const CodeBlock = ({ language, value }) => {
  return (
    <SyntaxHighlighter showLineNumbers={true} language={language}>
      {value}
    </SyntaxHighlighter>
  );
};

const Blog = ({ content, data }) => {
  const frontmatter = data;

  return (
    <>
      <h1>{frontmatter.title}</h1>
      <h3>{frontmatter.description}</h3>
      <ReactMarkdown
        escapeHtml={true}
        source={content}
        renderers={{ code: CodeBlock }}
      />
    </>
  );
};

export default Blog;

Blog.getInitialProps = async (context) => {
  const { blog } = context.query;
  // Import our .md file using the `slug` from the URL
  const content = await import(`../content/${blog}.md`);
  const data = matter(content.default);

  return { ...data };
};

Enter fullscreen mode Exit fullscreen mode

在内容目录中创建一个新文件conding-blog.md

---
slug: coding-blog
title: Coding blog
author: Imran Irshad
description: Coding Post For Beautiful Code
date: 30-September-2020
---

# React Functional Component
```

jsx
import React from "react";

const CoolComponent = () => <div>I'm a cool component!!</div>;

export default CoolComponent;

Enter fullscreen mode Exit fullscreen mode



Now If  Click `coding-blog`  
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/odmz8jspshglv9fbdg3j.png)

## Images

Create a new file in `content`  named `image-blog`



Enter fullscreen mode Exit fullscreen mode

降价

slug:image-blog
标题:图片博客
描述:查看图片在我们的博客中的样子

日期:2020年9月30日

图像

替代文本

结论

Nextjs 非常棒,而且非常灵活。你可以用它创建非常酷的东西。希望你从这篇文章中有所收获。

鏂囩珷鏉ユ簮锛�https://dev.to/imranib/build-a-next-js-markdown-blog-5777
PREV
最简单的自定义钩子来持久化数据。
NEXT
有效 Web 设计的 8 个基本原则 AWS 安全 LIVE!