Как создать блог с помощью Next.js и Markdown.

Здравствуйте, разработчики! Сегодня мы рассмотрим, как создать блог с помощью Next.js и Markdown!

Markdown

Markdown — это простой синтаксис, который используется для форматирования текста в заголовки, списки, курсив, полужирный и т.д. Markdown позволяет легко форматировать тексты. Синтаксис Markdown проще HTML. Файлы Markdown имеют расширение .md. Файл Readme, который мы используем в GitHub, использует Markdown.

Вот пример синтаксиса Markdown

# This is Heading1 <h1>
###### This is Heading6 <h6>
*Bullet Point
1.Numbered Point
_Italic text_
Войти в полноэкранный режим Выйти из полноэкранного режима

Итак, к этому моменту вы уже поняли, что делает Markdown. В нашем блог-сайте мы будем создавать сообщения в синтаксисе Markdown и отображать их на нашем сайте.

Начало работы

Давайте создадим свежее приложение Next.js, используя npx.

npx create-next-app myblog
cd myblog 

Войдите в полноэкранный режим Выход из полноэкранного режима

Мы создали новый проект Next.js с именем «myblog». Теперь откройте эту папку в вашем любимом редакторе кода. Я буду использовать здесь код VS.

Я использую Tailwind CSS для стилизации моих веб-страниц. Если вы предпочитаете Bootstrap или пишете свой CSS, вы можете продолжать в том же духе. Если вы хотите узнать, как добавить Tailwind CSS в ваше приложение Next.js, прочитайте мою статью здесь.

https://dev.to/anuraggharat/how-to-add-tailwind-css-in-next-js-3epn.

Итак, давайте разберемся, что мы будем создавать.

Мы создаем простой сайт, который будет иметь две страницы — Главная страница и Страница блога. На главной странице будет список всех блогов, а для каждого блога мы создадим отдельные статические страницы блога.

Итак, давайте начнем!

Настройка проекта.

Создайте две папки с именами components и posts в корневом каталоге вашего проекта. В папке Posts будут храниться все наши сообщения, которые будут написаны в Markdown, а в папке Components будут храниться все компоненты, которые мы будем использовать.

Теперь создайте папку blogs внутри папки pages. Внутри папки blogs добавьте страницу с именем [id].js. Мы будем использовать динамические маршруты для показа каждого поста. Если вы хотите узнать больше о динамических маршрутах, я бы посоветовал вам прочитать это. https://nextjs.org/docs/routing/dynamic-routes.

Вкратце о динамических маршрутах.

Next.js позволяет создавать динамические маршруты для файлов внутри квадратных скобок.

Так как у нас есть [id].js в папке posts, страница будет вызвана при вызове URL localhost:3000/blogs/1243 . Поскольку маршрут динамический, все, что будет после /blogs/, будет вызывать [id].js.
Далее в этом руководстве мы создадим статическую страницу для каждого поста, используя getStaticProps() и getStaticPaths().

Двигаясь дальше, очистите существующий код из файла index.js. После этого ваш файл index.js должен выглядеть следующим образом.

export default function Home() {
  return (
    <div>

    </div>
  )
}
Вход в полноэкранный режим Выход из полноэкранного режима

После выполнения всех этих действий у вас должно получиться что-то вроде этого.

Создание фиктивных постов в формате Markdown

Нам нужно как минимум 2 поста, чтобы показать их на нашем сайте и протестировать его. Поэтому давайте быстро создадим их.

Скопируйте следующий фиктивный код и создайте два файла в каталоге posts. Назовите файлы по имени заголовка, так как мы будем использовать имя файла в качестве ‘url-параметра’. Например, localhost:3000/blog/first-post

Я создаю два файла с одинаковым содержимым, но меняю только название, чтобы их можно было различать.

Мои файлы будут называться first-blog и second-blog. Не забудьте использовать расширение .md.

---
title: 'My First Blog of 2022'
metaTitle: 'My First blog of 2022'
metaDesc: 'How to make a blogging website using Next.js, Markdown and style it using TailwindCSS.'
socialImage: images/pic1.jpg
date: '2022-02-02'
tags:
  - nextjs
  - personal
  - health
  - work
---
# The main content
# One morning, when Gregor Samsa woke from troubled dreams.
One morning, when Gregor Samsa woke from troubled dreams, he found himself *transformed* in his bed into a horrible  [vermin](http://en.wikipedia.org/wiki/Vermin "Wikipedia Vermin"). He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections. The bedding was hardly able to cover **strong** it and seemed ready to slide off any moment. His many legs, pitifully thin compared with the size of the rest of him, link waved abouthelplessly as he looked. <cite>“What's happened to me?”</cite> he thought. It wasn't a dream. His room, a proper human room although a little too small, lay peacefully between its four familiar walls.</p>

## The bedding was hardly able to cover it.
Вход в полноэкранный режим Выход из полноэкранного режима
---
title: 'My Second Blog of 2022'
metaTitle: 'My Second blog of 2022'
metaDesc: 'How to make a blogging website using Next.js, Markdown and style it using TailwindCSS.'
socialImage: images/pic2.jpg
date: '2022-02-02'
tags:
  - nextjs
  - personal
  - health
  - work
---

# The main content

# One morning, when Gregor Samsa woke from troubled dreams.
One morning, when Gregor Samsa woke from troubled dreams, he found himself *transformed* in his bed into a horrible  [vermin](http://en.wikipedia.org/wiki/Vermin "Wikipedia Vermin"). He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections. The bedding was hardly able to cover **strong** it and seemed ready to slide off any moment. His many legs, pitifully thin compared with the size of the rest of him, link waved abouthelplessly as he looked. <cite>“What's happened to me?”</cite> he thought. It wasn't a dream. His room, a proper human room although a little too small, lay peacefully between its four familiar walls.</p>

## The bedding was hardly able to cover it.
Войти в полноэкранный режим Выход из полноэкранного режима

Ниже я добавил много другого контента. Вы можете пропустить его, так как это не имеет никакого значения.

Верхняя часть между «- — -» и «- — -» называется Frontmatter. По сути, это метаданные, которые не будут отображаться.

Создание сайта и его стилизация с помощью Tailwind CSS

В рамках этого блога мы создадим просто базовый, не слишком причудливый сайт с минимальными стилями. Но вы можете использовать свои творческие способности. Мы создадим главную страницу, на которой будут перечислены все блоги, и страницу блога, на которой будет показано содержание отдельных блогов. Итак, давайте начнем

Создайте компонент макета многократного использования в папке components и импортируйте его в файл _app.js.

Layout.js

import Link from "next/link";

function Layout({children}) {
  return (
    <div className="w-full min-h-screen ">
      <div className="flex flex-row h-16 justify-around align-middle">
        <h1 className="my-auto text-2xl font-mono">Simple Blog</h1>
        <Link href={`/`}>
          <a className="my-auto">Github Code</a>
        </Link>
      </div>
      <div className="container md:w-3/5 w-5/6 mx-auto mt-16">
        {children}
      </div>
    </div>
  );
}

export default Layout;
Вход в полноэкранный режим Выйти из полноэкранного режима

Мы создали простой макет, в котором мы сохранили заголовок и будем отображать дочерние элементы под ним.

Импортируйте этот компонент Layout.js в файл _app.js.

_app.js

import Layout from '../components/Layout'
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
  return (
    <Layout>
    <Component {...pageProps} />
  </Layout>
  ) 

}

export default MyApp
Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь мы создадим многоразовый компонент Blog card для отображения блогов на странице index.js. Поэтому создадим компонент Blogcard.js в папке components.

Blogcard.js

import Link from "next/link";

function Blogcard() {
  return (
    <div className="container w-100 mx-auto mb-16">
      <img
        className="w-3/4 rounded-lg mx-auto drop-shadow-lg"
        src="https://images.pexels.com/photos/675764/pexels-photo-675764.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500"
      />
      <Link href={'/'}>
        <h1 className="text-4xl font-semibold mt-4">
          Here is my first blog of the website
        </h1>
      </Link>
      <p className="text-gray-600 text-sm">2 Feb 2022</p>
      <p>
        This is just a static blog written to test the component structure.This
        is just a static blog written to test the component structure. is just a
        static blog written to test the component structure.
      </p>
    </div>
  );
}

export default Blogcard;
Вход в полноэкранный режим Выход из полноэкранного режима

В настоящее время для стилизации я добавил сюда фиктивные статические данные и изображения. Как только мы импортируем файлы разметки, мы добавим данные постов динамически. Пока что, чтобы проверить, как будет выглядеть наш блог, добавьте некоторые фиктивные данные.

Импортируйте карточку блога в файл index.js

index.js

import Blogcard from "../components/Blogcard";

export default function Home() {
  return (
    <div>
      <Blogcard />
      <Blogcard />
    </div>
  );
}
Войдите в полноэкранный режим Выйти из полноэкранного режима

Итак, теперь мы создали общую структуру нашего сайта. Теперь нам нужно отобразить содержимое постов на странице.

Загрузка постов на домашнюю страницу

Создайте папку images в папке public. Ранее мы использовали социальные изображения в нашей разметке, теперь это папка, в которой мы будем хранить все изображения. Я называю изображения «pic1» и «pic2», потому что именно так я назвал их в файлах разметки. У меня есть 2 красивых изображения, импортированных из Pexels.

Для извлечения содержимого нам понадобится пакет gray-matter. Поэтому давайте установим его с помощью «npm».

npm install gray-matter
Вход в полноэкранный режим Выход из полноэкранного режима

Что делает gray-matter?

«Разбирает front-matter из строки или файла. Быстрый, надежный и простой в использовании. По умолчанию парсит YAML front matter, но также поддерживает YAML, JSON, TOML или Coffee Front-Matter, с возможностью установки пользовательских разделителей. Используется в metalsmith, assemble, verb и многих других проектах».

Теперь откройте ваш файл index.js. Здесь мы будем импортировать содержимое markdown и разбирать его с помощью gray-matter.

Добавьте этот оператор import в index.js.

import fs from 'fs'
//FS to read files 
import matter from "gray-matter";
Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь мы будем использовать метод getStaticProps(), который представляет собой метод выборки данных, запускаемый только во время сборки, и передающий реквизиты странице.

export async function getStaticProps(){
  // Getting all our posts at build time

  // Get all the posts from posts folder
  const files = fs.readdirSync("posts");

  // Loop over each post to extract the frontmatter which we need
  const posts = files.map((file) => {
    // getting the slug here which we will need as a URL query parameter
    const slug = file.replace(".md", "");
    // Reading the contents of the file
    const filecontent = fs.readFileSync(`posts/${file}`, "utf-8");
    const parsedContent = matter(filecontent);
    //The parsed content contains data and content we only need the data which is the frontmatter
    const {data} = parsedContent
    return {
      slug,
      data,
    };
  });

  return {
    props:{
      posts
    }
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Если вы хотите узнать больше о выборке данных в Next.js, обязательно прочитайте мой другой блог.

Здесь мы получаем slugs из имени файла. Slugs будет служить в качестве Url-параметра для загрузки каждого поста. Затем мы считываем данные из каждого файла и разбираем их с помощью gray-matter. Мы де-структурируем данные из контента, поскольку нам нужны только те данные, которые являются frontmatter (мета-данные) из постов. Затем мы собираем все данные в массив posts и возвращаем их в качестве props на страницу index.js. Используйте консольные отчеты журнала, чтобы лучше понять, какая строка что возвращает.

Теперь соберем данные на странице index.js и передадим их компоненту ‘Blogcard’.

import Blogcard from "../components/Blogcard";
import fs from 'fs'
import matter from "gray-matter";

export default function Home(props) {
  const {posts} = props
  return (
    <div>

      {posts.map((post,index)=>(
        <Blogcard key={index} post={post} />
      ))}

    </div>
  );
}
export async function getStaticProps(){
  // Getting all our posts at build time

  // Get all the posts from posts folder
  const files = fs.readdirSync("posts");

  // Loop over each post to extract the frontmatter which we need
  const posts = files.map((file) => {
    // getting the slug here which we will need as a URL query parameter
    const slug = file.replace(".md", "");
    // Reading the contents of the file
    const filecontent = fs.readFileSync(`posts/${file}`, "utf-8");
    const parsedContent = matter(filecontent);
    //The parsed content contains data and content we only need the data which is the frontmatter
    const {data} = parsedContent
    return {
      slug,
      data,
    };
  });

  return {
    props:{
      posts
    }
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Мы получаем данные из функции getStaticProps() и извлекаем из них массив posts. Затем мы отображаем массив и передаем каждый элемент из массива в компонент «Blogcard».

В компоненте «Blogcard» мы извлекаем данные и отображаем их.

Blogcard.js

import Link from "next/link";

function Blogcard({post}) {
    console.log(post)
  return (
    <div className="container w-100 mx-auto mb-16">
      <img
        className="w-3/4 rounded-lg mx-auto drop-shadow-lg"
        src={post.data.socialImage}
      />
      <Link href={`blog/${post.slug}`}>
        <h1 className="text-4xl font-semibold mt-4">{post.data.metaTitle}</h1>
      </Link>
      <p className="text-gray-600 text-sm">{post.data.date}</p>
      <p>{post.data.metaDesc}</p>
    </div>
  );
}

export default Blogcard;
Вход в полноэкранный режим Выход из полноэкранного режима

Если все прошло успешно, вы получите два поста на главной странице. Если у вас возникнет какая-либо ошибка, проверьте, достигли ли передаваемые вами данные компонента или нет. Используйте журналы консоли для проверки каждого сегмента кода.

Вывод

Создание отдельных страниц для блогов

Теперь давайте создадим отдельные страницы для каждого блога. Мы создадим одну страницу, которая будет запускаться для каждого поста, и с помощью getStaticProps() и getStaticPaths() создадим отдельные статические страницы для каждого поста.

Вот как выглядит мой [id].js. Я переименовал функцию Blog.

export default function Blog() {
  return <div></div>;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Итак, откройте нашу страницу [id].js в папке blog и добавьте следующий код.

Импортируем gray-matter и файловую систему (fs)

import fs from 'fs';
import matter from 'gray-matter';
Войти в полноэкранный режим Выйти из полноэкранного режима

getStaticPaths()

export async function getStaticPaths() {
  // Get all the paths from slugs or file names
  const files = fs.readdirSync("posts");
  const paths = files.map((files) => ({
    params: {
      id: files.replace(".md", ""),
    },
  }));
  console.log("paths",paths)
  return {
    paths,
    fallback:false
  }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Итак, в этой функции мы генерируем массив всех допустимых путей. Эти пути являются именами slug, которые будут загружать записи блога с таким именем. Затем мы возвращаем эти пути вместе с fallback как false. Fallback покажет страницу 404 для неправильных URL. Подробнее о getStaticPaths() здесь

getStaticPaths()

export async function getStaticProps({params:{id}}){
    const fileName = fs.readFileSync(`posts/${id}.md`, "utf-8");
    const { data: frontmatter, content } = matter(fileName);
    return {
      props: {
        frontmatter,
        content,
      },
    };
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Эта функция похожа на функцию, которую мы написали на странице index.js для получения списка блогов. Разница лишь в том, что здесь мы находим один пост по id, переданному в URL, и возвращаем весь пост на страницу. Давайте выведем в консоль журнал и проверим, получаем ли мы содержимое.

export default function Blog({ frontmatter ,content}) {
  console.log(frontmatter)
  console.log(content);

  return <div></div>;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Ура! Мы получили содержимое! Но подождите, содержимое, которое мы получили, находится в формате markdown, и мы не можем напрямую показать его здесь. Для этого установите markdown-it.

npm i markdown-it
Войдите в полноэкранный режим Выход из полноэкранного режима

Этот пакет преобразует markdown в HTML-код, который мы можем затем отобразить на нашей веб-странице.

Добавьте несколько классов и отобразите содержимое, как показано ниже:


import fs from "fs";
import matter from "gray-matter";
import md from 'markdown-it'

export default function Blog({ frontmatter ,content}) {

  return (
    <div>
      <img src={`/${frontmatter.socialImage}`} className="w-3/4 mx-auto" />
      <div className="">
        <h1 className="text-3xl">{frontmatter.title}</h1>
        <div dangerouslySetInnerHTML={{ __html: md().render(content) }}></div>
      </div>
    </div>
  );
}

Войти в полноэкранный режим Выход из полноэкранного режима

Мы используем «dangerouslySetInnerHTML», чтобы применить наш HTML-контент на нашей веб-странице.

Первое впечатление — содержимое отображается, но что-то выглядит не так. Да, типографика еще не в порядке. Не волнуйтесь, Tailwind здесь, чтобы спасти нас. Установите этот плагин tailwind typography.

npm install -D @tailwindcss/typography
Войдите в полноэкранный режим Выход из полноэкранного режима

С помощью этого плагина вы можете задать «className» как «prose» для div, и он будет стилизовать все внутри этого div надлежащим образом.

После установки добавьте его в файл tailwind.config.js

module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [require("@tailwindcss/typography")],
};
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Теперь задайте className как prose самому внешнему div’у отрисованного содержимого.

В итоге ваш файл [id].js должен выглядеть примерно так.

import fs from "fs";
import matter from "gray-matter";
import md from 'markdown-it'

export default function Blog({ frontmatter ,content}) {

  return (
    <div className="w-100">
      <img src={`/${frontmatter.socialImage}`} className="w-3/4 mx-auto" />
      <div className="prose w-3/4  mx-auto">
        <h1 className="text-3xl">{frontmatter.title}</h1>
        <div dangerouslySetInnerHTML={{ __html: md().render(content) }}></div>
      </div>
    </div>
  );
}

export async function getStaticPaths() {
  // Get all the paths from slugs or file names
  const files = fs.readdirSync("posts");
  const paths = files.map((files) => ({
    params: {
      id: files.replace(".md", ""),
    },
  }));
  console.log("paths",paths)
  return {
    paths,
    fallback:false
  }
}

export async function getStaticProps({params:{id}}){
    const fileName = fs.readFileSync(`posts/${id}.md`, "utf-8");
    const { data: frontmatter, content } = matter(fileName);
    return {
      props: {
        frontmatter,
        content,
      },
    };
}
Вход в полноэкранный режим Выход из полноэкранного режима

И да, вы наконец-то создали свой личный блог, используя Next.js и Markdown. Вы можете использовать свои собственные творческие способности и стилизовать его.

Блог в моем личном портфолио также создан с помощью этого же метода. Посмотрите его здесь.

Портфолио

Я привожу ссылку на репозиторий GitHub, чтобы вы могли ознакомиться с ним! Спасибо, что прочитали. Следите за мной в twitter, я регулярно публикую материалы о веб-разработке и программировании. Счастливого кодинга!

GitHub — anuraggharat/Simple-Blog:

Оцените статью
devanswers.ru
Добавить комментарий