Next.js - это «метафреймворк» React (фреймворк, построенный на фреймворке) для разработки веб-приложений. Next.js стал популярным выбором для веб-разработчиков благодаря загрузочной среде React (похожей на create-react-app
) и простой файловой маршрутизации для написания внутреннего кода.
Next.js прост и гибок. По сравнению с полноценным генератором статических сайтов, разработчики не могут руководствоваться указаниями по реализации приложения или сайта. Благодаря такой гибкости в этой статье описывается только один подход к созданию простого блога, основанного на уценке. Берите то, что полезно 🤗, не обращайте внимания на остальное.
Если вы хотите пропустить и сослаться на окончательные версии стартера, не стесняйтесь проверить готовую реализацию 🦙.
Клонировать стартер
Давайте начнем. Я предоставил простой стартер, чтобы использовать его в качестве отправной точки для этого урока. Вы можете клонировать проект или посмотреть его на github для справки.
// clone the repo from your terminal $ git clone [email protected]:kendallstrautman/nextjs-blog-starter.git my-nextjs-blog
// install the dependencies $ cd my-nextjs-blog $ yarn install
// start up the dev server $ yarn dev
После клонирования проекта и запуска сервера разработки перейдите к http://localhost:3000/
в своем браузере, чтобы увидеть, с чем мы работаем.
Как видите, на данный момент это довольно просто. Если вы посмотрите на проект в редакторе кода, вы увидите следующую структуру каталогов:
src/
├── components/
├── data/
└── pages/
Структура проекта
Давайте посмотрим на файл pages/index.js
:
const Index = props => { return ( <Layout pathname="/" siteTitle={props.title} siteDescription={props.description}> <section> <BlogList /> </section> </Layout> ) }
export default Index
Index.getInitialProps = async function() { const configData = await import(`../data/config.json`) return { ...configData, } }
Вы увидите, что у нас есть Layout
компонент, обертывающий <section>
с BlogList
компонентом - это все части, которые пока визуализируют наш маленький начальный элемент.
Обработка данных
Обратите внимание на использование getInitialProps под компонентом. Next.js будет запускать эту функцию для облегчения рендеринга на стороне сервера или SSR. Когда Next загружает эту страницу, он запускает метод getInitialProps
, передает возвращаемое значение компоненту страницы в качестве свойств и отображает компонент на стороне сервера перед отправкой ответа браузеру.
Это ваш хлеб с маслом для получения данных на уровне страницы в Next. Вы можете использовать getInitialProps
получение данных из внешнего API или, как показано в этом примере, вы можете получить доступ к локальным источникам данных.
‹Tip› Примечание. этот метод работает только для компонентов, определенных в каталоге pages/
, т. Е. page
компонентов. Вы не можете использовать этот метод для дочерних компонентов, но вы можете передать полученные данные этим дочерним компонентам, как вы видите, как это делается с Layout
в приведенном выше примере. ‹/Tip›
Layout
передаются реквизиты, такие как название и описание сайта. Если вы посмотрите на данные в src/data/config.json
, вы увидите значения, на которые ссылаются эти реквизиты. Идите вперед и измените заголовок сайта на название вашего проекта, а затем посмотрите, как оно обновляется в заголовке.
Макет и стиль 🦋
Чтобы немного уменьшить масштаб, цель компонента Layout
- предоставить визуальный скелет для каждой страницы сайта. Обычно он содержит какой-то вид навигации или заголовок, который отображается на большинстве или на всех страницах, вместе с элементом нижнего колонтитула. В нашем случае у нас есть только заголовок, содержащий заголовок сайта.
Внутри Layout
есть компонент Meta
, который содержит все глобальные стили, а также все, что нужно поместить в ‹head› сайта в целях SEO или доступности. Обратите внимание, что использование компонента Layout
не является уникальным для Next.js; вы также увидите, что это часто используется на сайтах Гэтсби.
Одна вещь, которую вы можете заметить в компоненте Layout
, - это использование тега <style jsx>
. Next.js сразу работает с styled-jsx, изящным фреймворком css-in-js, созданным командой Zeit. Это супер-интуитивно понятно. Все стили привязаны к компоненту, и вы можете создавать динамические стили на основе свойств. Мир css-in-js - ваша устрица!
Единственным недостатком styled-jsx
является отсутствие поддержки вложенности, которая может вас беспокоить, а может и не беспокоить. Пока вы просто напишете старый добрый ванильный CSS, у вас все будет хорошо. Чтобы узнать больше о том, как использовать styled-jsx
, загляните в репозиторий styled-jsx на GitHub.
Еще раз обратите внимание, что глобальные стили и шрифты обрабатываются в компоненте Meta
через тег <style jsx global>
. Используйте это везде, где вам нужно реализовать глобальные стили.
Добавление каталога сообщений
Теперь, когда мы знакомы со структурой проекта и основами Next.js, давайте приступим к добавлению частей и частей, чтобы запустить и запустить блог Markdown.
Сначала добавьте новую папку с именем posts
в каталог src
. Вы можете добавить сюда все свои сообщения в блоге markdown. Если у вас еще нет готового контента, просто добавьте несколько фиктивных сообщений в блог. Мне нравится использовать Unsplash для образцов фотографий, а Cupcake, Hipsum или Sagan Ipsum - мои предпочтительные текстовые генераторы - сохраняет интересное 🧁.
Вот пример сообщения в блоге-заполнителе с некоторыми часто используемыми значениями внешнего вида.
---
title: The coastal red giants
author: Watson & Crick
date: 2019-07-10
hero_image: ../static/bali-15.jpg
---
Brain is the seed of intelligence something incredible is waiting to be known.
Кроме того, создайте папку static
в src
. Здесь вы будете хранить изображения.
Обработка файлов Markdown 🤖
Затем нам нужно установить несколько пакетов, которые будут обрабатывать наши файлы разметки.
$ yarn add raw-loader gray-matter react-markdown
Raw Loader обработает наши файлы уценки. Gray Matter проанализирует наши значения frontmatter yaml. А React Markdown проанализирует и отобразит тело наших файлов уценки.
Добавить конфигурацию Next.js
Теперь, когда мы установили несколько пакетов, необходимых для обработки уценки, нам нужно настроить использование raw-loader
, создав файл next.config.js в корне проекта. В этом файле мы будем обрабатывать любую настраиваемую конфигурацию для веб-пакета, маршрутизации, сборки и конфигурации времени выполнения, параметров экспорта и многого другого. В нашем случае мы просто добавим правило веб-пакета, чтобы использовать raw-loader
для обработки всех файлов уценки.
//next.config.js
module.exports = {
webpack: function(config) {
config.module.rules.push({
test: /\.md$/,
use: 'raw-loader',
})
return config
},
}
Страницы и динамическая маршрутизация
Итак, мы настроены на использование файлов разметки в нашем проекте. Давайте начнем кодировать страницу шаблона блога, которая будет отображать контент из этих файлов уценки в src/posts
.
Для некоторых базовых знаний каталог pages
является особенным в Next.js. Каждый .js
файл в этом каталоге будет отвечать на соответствующий HTTP-запрос. Например, когда запрашивается домашняя страница ('/'), будет отображаться компонент, экспортированный из pages/index.js
. Если вы хотите, чтобы на вашем сайте была страница /about
, просто создайте файл с именем pages/about.js
.
Это замечательно для статических страниц, но мы хотели бы иметь единый шаблон, на основе которого будут строиться все сообщения в блогах, с получением различных данных из каждого файла разметки. Это означает, что нам нужна какая-то динамическая маршрутизация, чтобы уникальные сообщения в блогах, использующие один и тот же шаблон, имели «красивые» URL-адреса и свои собственные отдельные страницы.
Динамические маршруты в Next.js обозначаются квадратными скобками []
в имени файла. В этих скобках мы можем передать параметр запроса компоненту страницы. Например, давайте создадим новую папку в src/posts
с именем blog
, затем добавим новый файл в эту папку блога [slug].js
, мы можем использовать все, что передается в качестве этого параметра slug
для динамического доступа к данным. Итак, если мы посетим http://localhost:3000/blog/julius-caesar
, все, что возвращается из компонента [slug].js
page, будет отображаться и будет иметь доступ к этому параметру запроса slug, то есть julius-caesar.
Получите данные Markdown для шаблона блога
При динамической маршрутизации мы можем использовать этот параметр slug, передав имя файла сообщения в блоге, а затем получая данные из соответствующего файла уценки через getInitialProps
.
import matter from 'gray-matter' import ReactMarkdown from 'react-markdown' import Layout from '../../components/Layout'
export default function BlogTemplate(props) { // data from getInitialProps const markdownBody = props.content const frontmatter = props.data return ( <Layout siteTitle={props.siteTitle}> <article> <h1>{frontmatter.title}</h1> <div> <ReactMarkdown source={markdownBody} /> </div> </article> </Layout> ) }
BlogTemplate.getInitialProps = async function(context) { // context contains the query param const { slug } = context.query // grab the file in the posts dir based on the slug const content = await import(`../../posts/${slug}.md`) // also grab the config file so we can pass down siteTitle const config = await import(`../../data/config.json`) //gray-matter parses the yaml frontmatter from the md body const data = matter(content.default) return { siteTitle: config.title, ...data, } }
В этом примере вы заметите, что мы используем gray-matter
и ReactMarkdown
для правильной обработки тела yaml frontmatter и markdown.
В увеличенном масштабе посмотрите, как это работает: при переходе к динамическому маршруту, например. http://localhost:3000/blog/julius-caesar
, компонент BlogTemplate в pages/blog/[slug].js
передается объекту запроса { slug: ‘julius-caesar’ }
. Когда вызывается метод getInitialProps
, этот объект запроса передается через контекст. Мы получаем это значение slug и затем ищем файл в каталоге posts
, который содержит то же имя файла. Как только мы получаем данные из этого файла, мы анализируем переднюю часть из тела уценки и возвращаем данные. Эти данные передаются в качестве свойств компоненту BlogTemplate
, который затем может отображать эти данные по мере необходимости.
Загляните в [slug] .js file в финальной версии моего начального блога, чтобы получить еще одно представление о том, как можно отображать данные этого блога и применять стили.
Получить данные для индекса блога
Давайте закончим этот простой блог, добавив соответствующие данные в компонент BlogList
для страницы Index
. Поскольку мы можем использовать только getInitialProps
в компонентах страницы, мы получим все данные блога в компоненте Index
, а затем передадим их как опору для BlogList
для рендеринга.
// src/pages/index.js Index.getInitialProps = async function() { const siteConfig = await import(`../data/config.json`) // get all .md files from the src/posts dir const posts = (context => { // grab all the files matching this context const keys = context.keys() // grab the values from these files const values = keys.map(context) // go through each file const data = keys.map((key, index) => { // Create slug from filename const slug = key .replace(/^.*[\\\/]/, '') .split('.') .slice(0, -1) .join('.') // get the current file value const value = values[index] // Parse frontmatter & markdownbody for the current file const document = matter(value.default) // return the .md content & pretty slug return { document, slug, } }) // return all the posts return data })(require.context('../posts', true, /\.md$/))
return { allBlogs: posts, ...siteConfig, } }
Это может показаться немного сложным, но давайте рассмотрим его постепенно. Не стесняйтесь ссылаться на этот блог за исходным кодом. Он использует функцию, предоставляемую Webpack, require.context (), которая позволяет нам создавать наш собственный контекст на основе трех параметров:
- каталог для соответствия,
- логический флаг для включения или исключения подкаталогов,
- регулярное выражение для сопоставления файлов.
require.context(directory, (useSubdirectories = false), (regExp = /^\.\//))
Создание «контекста» позволяет нам создать пространство, по сути, где мы можем выбрать все файлы, соответствующие регулярному выражению, из определенного каталога и преобразовать их в управляемые форматы, которые возвращаются компоненту в качестве свойств для рендеринга.
Теперь, когда у нас есть все данные блога, передайте их как опору компоненту BlogList
.
const Index = props => { return ( <Layout pathname="/" siteTitle={props.title} siteDescription={props.description}> <section> <BlogList allBlogs={props.allBlogs} /> </section> </Layout> ) }
export default Index
Затем вы можете просмотреть блоги и отобразить список в вашем BlogList
компоненте по мере необходимости. Не стесняйтесь проверить Компонент BlogList в моем стартовом приложении, чтобы увидеть, как можно обрабатывать эти данные.
Следующие шаги
После настройки блога или сайта-портфолио вам, скорее всего, понадобится система управления контентом, которая упростит редактирование и обновление ваших сообщений или данных. Следите за обновлениями в моем следующем блоге о настройке этого стартера с TinaCMS. А пока вы можете ознакомиться с нашей документацией по использованию Next.js с TinaCMS или разветвить готовый продукт, чтобы сразу же начать играть с TinaCMS.