Создание блога с помощью Next.js

От автора: в этой статье мы будем использовать Next.js для создания фреймворка статического блога с дизайном и структурой, вдохновленными Jekyll. Я всегда был большим поклонником того, как Jekyll упрощает настройку блога для новичков и в то же время обеспечивает продвинутым пользователям контроль над каждым аспектом блога.

С появлением в последние годы Next.js в сочетании с популярностью React появилась новая возможность для поиска статических блогов. Next.js упрощает создание статических веб-сайтов на основе своей файловой системы, практически не требуя настройки.

Структура каталогов типичного блога Jekyll с таким же видом выглядит следующим образом:

.
├─── _posts/ ...blog posts in markdown
├─── _layouts/ ...layouts for different pages
├─── _includes/ ...re-usable components
├─── index.md ...homepage
└─── config.yml ...blog config

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

Монтаж

Next.js построен на React и написан в Node.js. Поэтому нам нужно сначала установить npm, перед добавлением в проект next, react и react-dom.

mkdir nextjs-blog && cd $_
npm init -y
npm install next react react-dom --save

Чтобы запустить скрипты Next.js из командной строки, мы должны добавить команду next в раздел scripts файла package.json.

"scripts": { "dev": "next"
}

Теперь мы можем запустить в первый раз в командной строке npm run dev. Давай посмотрим что происходит.

$ npm run dev
> nextjs-blog@1.0.0 dev /~user/nextjs-blog
> next ready - started server on http://localhost:3000
Error: > Couldn't find a `pages` directory. Please create one under the project root

Компилятор жалуется на отсутствующий каталог страниц в корне проекта. Мы узнаем о концепции страниц в следующем разделе.

Концепция страниц

Next.js построен на концепции страниц. Каждая страница является компонентом React, который может иметь тип .js или .jsx, это сопоставляется с маршрутом на основе имени файла. Например:

File Route
---- -----
/pages/about.js /about
/pages/projects/work1.js /projects/work1
/pages/index.js /

Давайте создадим в корне проекта каталог pages и заполним нашу первую страницу index.js базовым компонентом React.

// pages/index.js
export default function Blog() { return <div>Welcome to the Next.js blog</div>
}

Выполните npm run dev еще раз, чтобы запустить сервер, и перейдите в браузере http://localhost:3000, чтобы впервые увидеть свой блог.

Создание блога с помощью Next.js

Из коробки мы получаем:

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

Статическая генерация всех страниц внутри каталога /pages/**.

Статический файл, служащий для активов, находящихся в каталоге /public/**.

Страницы ошибки 404.

Перейдите к произвольному пути на localhost, чтобы увидеть страницу 404 в действии. Если вам нужна пользовательская страница 404, в документации Next.js есть отличная информация.

Создание блога с помощью Next.js

Динамические страницы

Страницы со статическими маршрутами полезны для создания домашней страницы, страницы «О нас» и т. д. Однако для динамического построения всех наших сообщений мы будем использовать возможности динамических маршрутов Next.js. Например:

File Route
---- -----
/pages/posts/[slug].js /posts/1 /posts/abc /posts/hello-world

Любой маршрут, например /posts/1, /posts/abc и т. д., будет сопоставлен с /posts/[slug].js, а параметр slug будет отправлен на страницу в качестве параметра запроса. Это особенно полезно для постов в блоге, потому что мы не хотим создавать один файл на пост; вместо этого мы могли бы динамически передать слаг для рендеринга соответствующего поста.

Структура блога

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

.
├─ api
│ └─ index.js # fetch posts, load configs, parse .md files etc
├─ _includes
│ ├─ footer.js # footer component
│ └─ header.js # header component
├─ _layouts
│ ├─ default.js # default layout for static pages like index, about
│ └─ post.js # post layout inherts from the default layout
├─ pages
│ ├─ index.js # homepage
| └─ posts # posts will be available on the route /posts/
| └─ [slug].js # dynamic page to build posts
└─ _posts ├─ welcome-to-nextjs.md └─ style-guide-101.md

API блога

Базовая структура блога требует двух функций API:

Функция для извлечения метаданных всех постов в каталоге _posts

Функция для извлечения одного поста для данного slug с полным HTML и метаданными

По желанию, мы также хотели бы, чтобы вся конфигурация сайта, определенная в config.yml, была доступна для всех компонентов. Поэтому нам нужна функция, которая будет анализировать конфигурацию YAML в нативный объект.

Поскольку мы будем иметь дело со многими файлами, не относящимися к JavaScript, такими как Markdown (.md), YAML (.yml) и т. д., мы будем использовать библиотеку raw-loader для загрузки таких файлов в виде строк, чтобы упростить их обработку.

npm install raw-loader --save-dev

Далее нам нужно указать Next.js использовать raw-loader при импорте форматов файлов .md и .yml путем создания файла next.config.js в корне проекта (подробнее об этом).

module.exports = { target: 'serverless', webpack: function (config) { config.module.rules.push({test: /\.md$/, use: 'raw-loader'}) config.module.rules.push({test: /\.yml$/, use: 'raw-loader'}) return config }
}

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

{ "compilerOptions": { "baseUrl": "./", "paths": { "@includes/*": ["_includes/*"], "@layouts/*": ["_layouts/*"], "@posts/*": ["_posts/*"], "@api": ["api/index"], } }
}

Например, это позволяет нам импортировать макеты, просто используя:

import DefaultLayout from '@layouts/default'

Получение всех постов

Эта функция будет читать все файлы Markdown в каталоге _posts, анализировать основной материал, определенный в начале поста, с использованием gray-matter и возвращать массив метаданных для всех постов.

// api/index.js
import matter from 'gray-matter'


export async function getAllPosts() { const context = require.context('../_posts', false, /\.md$/) const posts = [] for(const key of context.keys()){ const post = key.slice(2); const content = await import(`../_posts/${post}`); const meta = matter(content.default) posts.push({ slug: post.replace('.md',''), title: meta.data.title }) } return posts;
}

Стандартный пост в Markdown выглядит так:

---
title: "Welcome to Next.js blog!"
---
**Hello world**, this is my first Next.js blog post and it is written in Markdown.
I hope you like it!

Раздел, обозначенный меткой —, называется основным материалом, который содержит метаданные поста, такие как заголовок, постоянная ссылка, теги и т. д. Вот вывод:

[ { slug: 'style-guide-101', title: 'Style Guide 101' }, { slug: 'welcome-to-nextjs', title: 'Welcome to Next.js blog!' }
]

Убедитесь, что вы сначала установили библиотеку gray-matter из npm, используя команду npm install gray-matter —save-dev.

Получение одного поста

Для данного слага эта функция найдет файл в каталоге _posts, проанализирует Markdown с помощью библиотеки marked и вернет выходной HTML с метаданными.

// api/index.js
import matter from 'gray-matter'
import marked from 'marked'


export async function getPostBySlug(slug) { const fileContent = await import(`../_posts/${slug}.md`) const meta = matter(fileContent.default) const content = marked(meta.content) return { title: meta.data.title, content: content }
}

Пример вывода:

{ title: 'Style Guide 101', content: '<p>Incididunt cupidatat eiusmod ...</p>'
}

Сначала убедитесь, что вы установили библиотеку marked из npm, используя команду npm install marked —save-dev.

Конфигурация

Чтобы повторно использовать конфигурацию Jekyll для нашего блога на Next.js, мы проанализируем файл YAML с помощью библиотеки js-yaml и экспортируем эту конфигурацию, чтобы ее можно было использовать для компонентов.

// config.yml
title: "Next.js blog"
description: "This blog is powered by Next.js"


// api/index.js
import yaml from 'js-yaml'
export async function getConfig() { const config = await import(`../config.yml`) return yaml.safeLoad(config.default)
}

Убедитесь, что вы сначала установили из npm js-yaml, используя команду npm install js-yaml —save-dev.

Включения

Каталог _includes содержит два основных компонента React, <Header> и <Footer>, которые будут использоваться в различных компонентах макета, определенных в каталоге _layouts.

// _includes/header.js
export default function Header() { return <header><p>Blog | Powered by Next.js</p></header>
}


// _includes/footer.js
export default function Footer() { return <footer><p>©2020 | Footer</p></footer>
}

Макеты

У нас есть два компонента макета в каталоге _layouts. Одним из них является <DefaultLayout>, который является базовым макетом, поверх которого будут построены все остальные компоненты макета.

// _layouts/default.js
import Head from 'next/head'
import Header from '@includes/header'
import Footer from '@includes/footer'


export default function DefaultLayout(props) { return ( <main> <Head> <title>{props.title}</title> <meta name='description' content={props.description}/> </Head> <Header/> {props.children} <Footer/> </main> )
}

Второй макет — это компонент <PostLayout>, который переопределяет заголовок, заданный в заголовке поста <DefaultLayout>, и отображает HTML-код поста. Он также содержит ссылку на домашнюю страницу.

// _layouts/post.js
import DefaultLayout from '@layouts/default'
import Head from 'next/head'
import Link from 'next/link'


export default function PostLayout(props) { return ( <DefaultLayout> <Head> <title>{props.title}</title> </Head> <article> <h1>{props.title}</h1> <div dangerouslySetInnerHTML={{__html:props.content}}/> <div><Link href='/'><a>Home</a></Link></div> </article> </DefaultLayout> )
}

next/head является встроенным компонентом для добавления элементов в head страницы. next/link является встроенным компонентом, который обрабатывает переходы на стороне клиента между маршрутами, определенными в каталоге страниц.

Главная страница

Как часть главной страницы, или домашней страницы, мы выведем перечень всех постов внутри каталога _posts. Список будет содержать заголовок поста и постоянную ссылку на страницу отдельного поста. Страница индекса будет использовать <DefaultLayout> и мы импортируем конфигурацию на главную страницу, чтобы передать в макет title и description.

// pages/index.js
import DefaultLayout from '@layouts/default'
import Link from 'next/link'
import { getConfig, getAllPosts } from '@api'


export default function Blog(props) { return ( <DefaultLayout title={props.title} description={props.description}> <p>List of posts:</p> <ul> {props.posts.map(function(post, idx) { return ( <li key={idx}> <Link href={'/posts/'+post.slug}> <a>{post.title}</a> </Link> </li> ) })} </ul> </DefaultLayout> )
} 

export async function getStaticProps() { const config = await getConfig() const allPosts = await getAllPosts() return { props: { posts: allPosts, title: config.title, description: config.description } }
}

getStaticProps вызывается во время сборки для предварительного рендеринга страниц, передавая props компоненту страницы по умолчанию. Мы используем эту функцию для получения списка всех постов во время сборки и рендеринга архива постов на главной странице.

Создание блога с помощью Next.js

Страница поста

На этой странице будет отображаться заголовок и содержание поста для slug предоставленного, как часть context. Страница поста будет использовать компонент <PostLayout>.

// pages/posts/[slug].js
import PostLayout from '@layouts/post'
import { getPostBySlug, getAllPosts } from "@api"


export default function Post(props) { return <PostLayout title={props.title} content={props.content}/>
}


export async function getStaticProps(context) { return { props: await getPostBySlug(context.params.slug) }
}


export async function getStaticPaths() { let paths = await getAllPosts() paths = paths.map(post => ({ params: { slug:post.slug } })); return { paths: paths, fallback: false }
}

Если страница имеет динамические маршруты, Next.js должен знать во время сборки все возможные пути. getStaticPaths предоставляет список путей, которые должны быть отображены в HTML во время сборки. Свойство fallback гарантирует, что если вы посетите маршрут, которого нет в списке путей, он вернет страницу 404.

Создание блога с помощью Next.js

Подготовка к производству

Добавьте следующие команды для build и start в package.json, раздел scripts, а затем запустите npm run build, затем npm run start, чтобы собрать статический блог и запустить сервер производства.

// package.json
"scripts": { "dev": "next", "build": "next build", "start": "next start"
}

Весь исходный код этой статьи доступен в репозитории GitHub. Вы можете клонировать его локально и поэкспериментировать. Репозиторий также включает в себя некоторые основные заполнители для применения CSS к вашему блогу.

Улучшения

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

Пагинация

Подсветка синтаксиса

Категории и теги для постов

Стайлинг

В целом, Next.js выглядит очень перспективным для создания статичных сайтов, таких как блог. В сочетании с возможностью экспортировать статический HTML мы можем создать по-настоящему автономное приложение без использования сервера!

Автор: Pankaj Parashar

Источник: https://css-tricks.com

Редакция: Команда webformyself.