使用Astro和MDX创建博客
#html #网络开发人员 #astro #markdown

为什么?

自从我开始写博客以来,我想在我的个人网站thomasledoux.be上这样做。
由于在remix&next.js中设置Markdown/MDX支持似乎很困难(我以前为我的网站使用的两个框架),因此我选择在dev.to上写我的博客。
一段时间的运行良好,但是现在我想对博客的布局,分析和获得MDX支持更多。
因此,我开始构建自己的博客平台,然后选择使用Astro

进行此操作。

如何?

设置Astro

设置Astro非常简单。
您可以在终端中运行npm create astro@latest,按照步骤操作,然后将项目启动并在几秒钟内运行。
一旦您的Astro Project设置了,我们将要做的接下来是添加MDX支持。
您可以通过Astro中的Integration添加MDX支持。
这就像在终端中运行npx astro add mdx一样容易。

创建MDX博客

安装了MDX集成后,您可以在应用程序中使用MDX组件和页面。
您可以通过在/src/pages文件夹中添加文件夹/blog开始。
在此文件夹中,您可以创建您的.mdx文件,例如best-features-nextjs-conf-2021.mdx
在MDX文件的顶部,您可以添加frontmatter属性。
您可以设置一个layout,这将使您的博客内容在给定的layout内部呈现。
frontmatter属性中,您还可以添加自定义属性,例如titledatetags,...
这看起来像:

---
title: "'The 3 best features announced at Next.js Conf 2021';"
layout: '../../layouts/BlogLayout.astro';
tags: ['nextjs', 'javascript'];
date: '2021-06-15T15:14:39.004Z';
---

当您在另一个组件/页面中导入MDX组件/页面时,或使用Astro.glob()从文件系统读取MDX文件时。
您可以使用此博客概述页面显示标题,创建日期和博客中的标签。

---
let posts = await Astro.glob('./*.mdx');
/* output: 
[
  {
    title: 'The 3 best features announced at Next.js Conf 2021',
    layout: '../../layouts/BlogLayout.astro',
    tags: ['nextjs', 'javascript'],
    date: '2021-06-15T15:14:39.004Z'
  }
]
*/
---

<section>
  {
    posts.map(post => (
      <article>
        {post.frontmatter.title} - {post.frontmatter.title}
        <div class="flex gap-x-4">
          {post.frontmatter.tags.map(tag => (
            <span>{tag}</span>
          ))}
        </div>
      </article>
    ))
  }
</section>

为博客文章添加阅读时间

Astro使您可以轻松地将RemarkRehype插件添加到您的降价中。
您可以将markdown属性添加到astro配置文件,将功能/插件添加到remarkPlugins属性(添加extendDefaultPlugins属性以确保默认插件不会被此配置更改覆盖):

import { remarkReadingTime } from './src/utils/calculate-reading-time.mjs';
export default defineConfig({
  integrations: [mdx()],
  markdown: {
    remarkPlugins: [remarkReadingTime],
    extendDefaultPlugins: true,
  },
});

./src/utils/calculate-reading-time.mjs文件看起来像这样:

import getReadingTime from 'reading-time';
import { toString } from 'mdast-util-to-string';

export function remarkReadingTime() {
  return function (tree, { data }) {
    const textOnPage = toString(tree);
    const readingTime = getReadingTime(textOnPage);
    // readingTime.text will give us minutes read as a friendly string,
    // i.e. "3 min read"
    data.astro.frontmatter.minutesRead = readingTime.text;
  };
}

因此,您将使用2个外部库reading-timemdast-util-to-string进行此工作。不要忘记对这些!
将其添加到Astro配置中,使此数据可在所有MDX文件的frontmatter中可用。
您现在可以在我们的博客概述中开始使用此信息:

<section>
  {
    posts.map(post => (
      <article>
        <h2>
          {post.frontmatter.title} - {post.frontmatter.date}
        </h2>
        <div class="flex gap-x-4">
          {post.frontmatter.tags.map(tag => (
            <span>{tag}</span>
          ))}
        </div>
        <p>{post.frontmatter.minutesRead}ing time</p>
      </article>
    ))
  }
</section>

设置SEO(元标记,OG:图像)

因为我们定义了要在MDX页面上使用的layout,因此我们可以开始使用layout内的MDX页面的frontmatter属性,因为它们已通过Astro.props对象传递。

然后可以使用这些frontmatter属性来添加诸如<title>和og之类的东西:image <meta> tag:

---
const { frontmatter } = Astro.props;
---

<head>
  <title>{frontmatter.title}</title>
  <meta content={frontmatter.title} property="og:title" />
  <meta content={frontmatter.title} property="twitter:title" />
  <meta name="twitter:card" content="summary_large_image" />
  <meta
    content={`https://website-thomas.vercel.app/api/og?title=${frontmatter.title}`}
    property="og:image"
  />
</head>

请注意,在示例中,我正在使用不同域的API路由,这是因为我使用@vercel/og软件包来基于我的博客文章的标题来生成og:image。您可以阅读文档here
我还根据Vercel提供的示例为背景添加了一些闪光。
og:image代的代码看起来像这样(在M​​y Next.js site上的Next.js API路由中创建):
我正在使用Inter字体,因此,如果您也想使用此字体,请务必确保download the font files并将其包含在您的项目中。

import {ImageResponse} from '@vercel/og'

export const config = {
  runtime: 'experimental-edge',
}

const font = fetch(new URL('../../assets/Inter.ttf', import.meta.url)).then(
  res => res.arrayBuffer(),
)

export default function handler(req) {
  const fontData = await font;

  try {
    const {searchParams} = new URL(req.url)
    const hasTitle = searchParams.has('title')
    const title = hasTitle
      ? searchParams.get('title')?.slice(0, 100)
      : 'My default title'

    return new ImageResponse(
      (
        <div
          style={{
            background: 'white',
            width: '100%',
            height: '100%',
            display: 'flex',
            textAlign: 'center',
            alignItems: 'center',
            justifyContent: 'center',
            flexDirection: 'column',
            fontFamily: 'Inter',
            backgroundImage:
              'radial-gradient(circle at 25px 25px, lightgray 2%, transparent 0%), radial-gradient(circle at 75px 75px, lightgray 2%, transparent 0%)',
            backgroundSize: '100px 100px',
          }}
        >
          <div
            style={{
              width: '80%',
              display: 'flex',
              flexDirection: 'column',
              textAlign: 'center',
              alignItems: 'center',
            }}
          >
            <p style={{fontSize: 32}}>Thomas Ledoux&apos;s blog</p>
            <p style={{fontSize: 64}}>{title}</p>
          </div>
        </div>
      ),
      {
        width: 1200,
        height: 600,
        fonts: [
          {
            name: 'Inter',
            data: fontData,
            style: 'normal',
          },
        ],
      },
    )
  } catch (e) {
    console.log(`${e.message}`)
    return new Response(`Failed to generate the image`, {
      status: 500,
    })
  }
}

就是这样,我的博客现在托管在我自己的网站上!
https://www.thomasledoux.be/blog检查一下。
在下一篇博客文章中,我将更深入地了解如何在博客上添加一个用于评论的系统,以及使用Prisma和Planetscale进行DB的分析。