Создание простого блога на Next.js, который рендерит Markdown

Зачем собирать блог вручную
Low-code инструменты (например, WordPress) упрощают запуск блога — готовые темы и интерфейсы позволяют писать посты за часы. Но если вам важен контроль над кодом, производительностью и структурой контента, стоит сделать блог с нуля. Next.js даёт готовые механизмы для статической генерации и интеграции React-компонентов, что упрощает работу с Markdown.
Важно: “фронт‑маттер“ — это YAML‑блок в начале Markdown‑файла, где задаются метаданные поста (заголовок, дата, теги и т. д.).
Что потребуется
- Node.js (LTS) и npm или yarn
- Базовые знания React и Next.js
- Папка проекта, структура с корнем проекта
- Пакеты: react-markdown и gray-matter
Совет: создайте репозиторий и ветку для начальной реализации, чтобы можно было откатить изменения.
Создание проекта Next.js
Самый простой способ стартовать — выполнить в терминале:
npx create-next-app markdown-blog
Это создаст проект со всем необходимым каркасом. Очистите файл index.js, чтобы он выглядел лаконично — в примере ниже показан минимальный шаблон (сохраните его в проекте):
import Head from 'next/head'
import styles from '../styles/Home.module.css'
export default function Home() {
return (
Create Next App
)
}
Структура контента и формат Markdown
Создайте в корне проекта папку content (или posts) для хранения Markdown‑файлов. Каждый файл будет отдельным постом, а имя файла — частью URL.
Пример файла: content/create-active-link-nextjs.md
---
title: How To create an active link in Nextjs
description: Customizing active links using useRouter()
isPublished: true
publishedDate: 2022/07/22
tags:
- next
---
## Main content
Обратите внимание на блок между строками с трёхточиями — это фронт‑маттер. Поле isPublished удобно для фильтрации постов на этапе сборки.
Установка библиотек для парсинга
Для парсинга Markdown и фронт‑маттера установите:
npm install react-markdown gray-matter
- react-markdown — компонент, который безопасно конвертирует Markdown в React/HTML.
- gray-matter — парсер фронт‑маттера (YAML → объект).
Утилиты для работы с файлами Markdown
Создайте папку utils и файл md.js. В нём разместим вспомогательные функции для чтения файлов, получения списка постов и извлечения одного поста.
Код (пример):
import fs from "fs";
import path from "path";
import matter from "gray-matter";
export const getPath = (folder:string) => {
return path.join(process.cwd(), `/${folder}`); // Get full path
}
export const getFileContent = (filename:string, folder:string) => {
const POSTS_PATH = getPath(folder)
return fs.readFileSync(path.join(POSTS_PATH, filename), "utf8");
};
export const getAllPosts = (folder:string) => {
const POSTS_PATH = getPath(folder)
return fs
.readdirSync(POSTS_PATH) // get files in directory
.filter((path) => /\.md?$/.test(path)) // only .md files
.map((fileName) => { // map over each file
const source = getFileContent(fileName, folder); // retrieve the file contents
const slug = fileName.replace(/\.md?$/, ""); // get the slug from the filename
const { data } = matter(source); // extract frontmatter
return {
frontmatter: data,
slug: slug,
};
});
};
Пояснения к функциям:
- getPath — возвращает абсолютный путь к папке с контентом.
- getFileContent — читает файл и возвращает его содержимое как строку.
- getAllPosts — считывает все .md‑файлы в папке, извлекает фронт‑маттер и формирует массив объектов { frontmatter, slug }.
Фильтрация опубликованных постов
Чтобы возвращать только посты с isPublished: true, используйте фильтр:
export const getAllPublished = (folder:string) => {
const posts = getAllPosts(folder)
const published = posts.filter((post) => {
return post.frontmatter.isPublished === true
})
return published
}
Этот helper полезен для отображения списка постов в домашней странице.
Получение одного поста
Добавьте функцию для извлечения полного содержимого поста (фронт‑маттер + тело Markdown):
export const getSinglePost = (slug:string, folder:string) => {
const source = getFileContent(`${slug}.md`, folder);
const { data: frontmatter, content } = matter(source);
return {
frontmatter,
content,
};
};
Эта функция считывает файл по slug и возвращает frontmatter и raw Markdown (content).
Отображение списка постов (index.js)
Next.js поддерживает статическую генерацию. getStaticProps вызывается во время сборки и возвращает props компоненту страницы.
Пример использования getStaticProps и перечисления постов:
export const getStaticProps = async () => {
const posts = getAllPublished("posts");
return {
props: { posts },
};
};
Измените компонент Home, чтобы он отображал список постов:
import Head from "next/head";
import Link from "next/link";
import { getAllPublished } from "../utils/md";
function Home({ posts }) {
return (
Create Next App
{posts.map((post) => (
[ {post.frontmatter.tags.join(", ")} ]
{post.frontmatter.title}
{" "}
{post.frontmatter.description}
))}
);
}
export const getStaticProps = async () => {
const posts = getAllPublished("content");
return {
props: { posts },
};
};
export default Home;
Примечание: в примере getAllPublished вызывается с аргументом “content” — используйте имя папки, где вы реально храните свои .md‑файлы.
Генерация динамических маршрутов и отображение поста
Для генерации путей используйте getStaticPaths, а затем getStaticProps для каждого пути.
export const getStaticPaths = async () => {
const paths = getAllPublished("posts").map(({ slug }) => ({ params: { slug } }));
return {
paths,
fallback: false,
};
};
Затем — получение данных для конкретного поста:
export const getStaticProps = async ({ params }) => {
const post = await getSinglePost(params.slug, "posts");
return {
props: { ...post },
};
};
Компонент для рендера Markdown с помощью react-markdown:
import ReactMarkdown from 'react-markdown'
import { getAllPosts, getSinglePost } from "../../utils/md";
const Post = ({ content, frontmatter }) => {
return (
{frontmatter.tags.join(', ')}
{frontmatter.title}
{frontmatter.publishedDate}
{content}
);
};
Это отобразит заголовок, дату, теги и преобразованный HTML из Markdown.
Подсветка синтаксиса для dev‑постов
Для блога разработчика удобно добавить подсветку синтаксиса. Популярные варианты:
- rehype‑highlight или remark‑prism (плагины для remark/rehype)
- prismjs или highlight.js в связке с компонентом для рендера кода
Примерный путь: подключить плагин в трансформer react‑markdown (rehypePlugins/remarkPlugins), и добавить CSS темы Prism.
Стилизация
Next.js поддерживает CSS-модули, глобальные CSS и CSS-in-JS. Для простоты можно использовать CSS‑модули: создайте файл styles/Blog.module.css и импортируйте его в компоненты.
Если нужна более богатая типографика для статей — используйте готовые типографические CSS (например, типографические наборы для article) или Tailwind CSS.
Альтернативные подходы
- MDX: позволяет вставлять React‑компоненты прямо в Markdown (remark-mdx). Полезно, если посты содержат интерактивные виджеты.
- Headless CMS (Contentful, Sanity, Strapi): удобны для редакторов и для удалённого управления контентом. Подходит, если у вас не один автор.
- База данных/API: хранить контент в базе и рендерить динамически (полезно для очень большого количества постов).
Когда ручное хранение Markdown может не подойти: нужна поддержка WYSIWYG для редакторов, права доступа и версионирование — тогда headless CMS удобнее.
Паттерны и ментальные модели
- “Контент как код”: храните статьи в Git, используйте PR для изменений — это даёт версионирование и контроль качества.
- “Статическая генерация для публичного контента”: если контент редко меняется, статическая генерация даёт лучшую производительность и SEO.
- “IsPublished флаг”: храните черновики локально и публикуйте через isPublished, чтобы не удалять файлы.
Критерии приёмки
- Список постов корректно отображается на главной странице.
- По клику открывается страница поста с корректным URL (slug).
- Markdown корректно конвертируется в HTML, включая заголовки, списки и код.
- Только посты с isPublished: true видны на публичных страницах.
- Сборка проекта (npm run build) проходит без ошибок.
Чеклист перед деплоем
- Проверить, что все публичные посты имеют корректные метаданные (title, description, publishedDate).
- Убедиться, что getStaticPaths возвращает все ожидаемые пути.
- Добавить SEO‑теги (meta title, description, og:). Для каждой страницы можно генерировать метаданные из фронт‑маттера.
- Подключить систему подсветки кода и проверить примеры кода в постах.
- Протестировать сборку и обновление контента (локально и на CI).
Ролевые чеклисты
Автор:
- Проверить орфографию и форматирование Markdown
- Указать теги, краткое описание и дату
Разработчик:
- Настроить сборку и CI
- Обеспечить обработку ошибок при чтении файлов
Операции/Мейнтейнер:
- Настроить резервное копирование репозитория
- Обновлять зависимости (react-markdown, gray-matter) и следить за CVE
Точки отказа и способы защиты
- Ошибка чтения файла: обрабатывать fs.readFileSync в try/catch и логировать путь файла.
- Некорректный фронт‑маттер: валидировать поля (title, publishedDate) и при ошибке помечать как draft.
- XSS: react-markdown по умолчанию безопаснее, но при подключении плагинов для raw HTML проверяйте ввод.
Миграция и совместимость
- Если вы переходите с CMS: экспортируйте статьи в Markdown или в формате, который можно конвертировать в фронт‑маттер + content.
- При смене структуры папок обновите аргументы getAllPublished/getSinglePost (имя папки).
Пример маленькой методологии публикации
- Автор создаёт .md в ветке feature/post-title.
- PR проходит ревью кода и контента.
- Мерж в main запускает CI сборку и деплой статической версии.
- Если нужна горячая корректировка — правка фронт‑маттера и повторный merge.
Короткая галерея крайних случаев
- Очень большой пост (несколько мегабайт) — подумайте о разбивке на части и lazy‑loading.
- Много авторов — используйте headless CMS или workflow с PR и review apps.
1‑строчная глоссарий
- Frontmatter — метаданные в начале Markdown‑файла, обычно в YAML.
- Slug — имя файла без расширения, используется в URL.
- getStaticProps / getStaticPaths — функции Next.js для статической генерации.
Последние рекомендации по SEO и social
- Для каждой статьи генерируйте title и meta description из frontmatter.
- Добавьте Open Graph теги и картинку для превью.
Выгляд для социальных сетей (пример):
- OG title: используйте title из фронт‑маттера
- OG description: используйте description из фронт‑маттера
Короткое резюме
- Next.js + Markdown — простой путь для статического блога с версионированием.
- gray-matter извлекает фронт‑маттер, react-markdown рендерит контент.
- Продумайте workflow публикации, тесты и безопасность входных данных.
Важное: если ваш проект будет расти и появятся требования к редактированию контента через UI, рассмотрите внедрение headless CMS или MDX.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone