От автора: в этой статье мы будем использовать 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, чтобы впервые увидеть свой блог.
Из коробки мы получаем:
Горячая перезагрузка, поэтому нам не нужно обновлять браузер при каждом изменении кода.
Статическая генерация всех страниц внутри каталога /pages/**.
Статический файл, служащий для активов, находящихся в каталоге /public/**.
Страницы ошибки 404.
Перейдите к произвольному пути на localhost, чтобы увидеть страницу 404 в действии. Если вам нужна пользовательская страница 404, в документации 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 компоненту страницы по умолчанию. Мы используем эту функцию для получения списка всех постов во время сборки и рендеринга архива постов на главной странице.
Страница поста
На этой странице будет отображаться заголовок и содержание поста для 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.
Подготовка к производству
Добавьте следующие команды для 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.