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.