Einen Blog mit Next.js bauen

Avatar of Pankaj Parashar
Pankaj Parashar am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

In diesem Artikel werden wir Next.js verwenden, um ein statisches Blog-Framework mit einem Design und einer Struktur zu erstellen, die von Jekyll inspiriert sind. Ich war schon immer ein großer Fan davon, wie Jekyll es Anfängern erleichtert, einen Blog einzurichten, und gleichzeitig auch fortgeschrittenen Benutzern ein hohes Maß an Kontrolle über jeden Aspekt des Blogs bietet.

Mit der Einführung von Next.js in den letzten Jahren, kombiniert mit der Popularität von React, gibt es einen neuen Weg, statische Blogs zu erkunden. Next.js macht es super einfach, statische Websites zu erstellen, die auf dem Dateisystem selbst basieren und wenig bis gar keine Konfiguration erfordern.

Die Verzeichnisstruktur eines typischen, minimalistischen Jekyll-Blogs sieht so aus

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

Die Idee ist, unser Framework so weit wie möglich an dieser Verzeichnisstruktur auszurichten, damit es einfacher wird, einen Blog von Jekyll zu migrieren, indem einfach die Beiträge und Konfigurationen des Blogs wiederverwendet werden.

Für diejenigen, die Jekyll nicht kennen: Es ist ein statischer Seitengenerator, der Ihren einfachen Text in statische Websites und Blogs umwandeln kann. Beziehen Sie sich auf die Schnellstartanleitung, um mit Jekyll loszulegen.

Dieser Artikel setzt auch voraus, dass Sie über grundlegende Kenntnisse in React verfügen. Wenn nicht, ist die Startseite von React ein guter Ort, um zu beginnen.

Installation

Next.js wird von React angetrieben und in Node.js geschrieben. Wir müssen also zuerst npm installieren, bevor wir next, react und react-dom zum Projekt hinzufügen.

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

Um Next.js-Skripte über die Befehlszeile auszuführen, müssen wir den Befehl next in den Abschnitt scripts unserer package.json einfügen.

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

Wir können nun zum ersten Mal npm run dev in der Befehlszeile ausführen. Mal sehen, was passiert.

$ npm run dev
> [email protected] dev /~user/nextjs-blog
> next

ready - started server on https://:3000
Error: > Couldn't find a `pages` directory. Please create one under the project root

Der Compiler beschwert sich über ein fehlendes Verzeichnis pages im Stammverzeichnis des Projekts. Das Konzept der Seiten lernen wir im nächsten Abschnitt.

Konzept der Seiten

Next.js ist um das Konzept der Seiten aufgebaut. Jede Seite ist eine React-Komponente, die vom Typ .js oder .jsx sein kann und basierend auf dem Dateinamen einer Route zugeordnet wird. Zum Beispiel

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

Erstellen wir das Verzeichnis pages im Stammverzeichnis des Projekts und füllen unsere erste Seite, index.js, mit einer einfachen React-Komponente.

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

Führen Sie npm run dev erneut aus, um den Server zu starten, und navigieren Sie zu https://:3000 im Browser, um Ihren Blog zum ersten Mal anzuzeigen.

Screenshot of the homepage in the browser. The content says welcome to the next.js blog.

Out of the box erhalten wir

  • Hot Reloading, damit wir den Browser nicht bei jeder Codeänderung aktualisieren müssen.
  • Statische Generierung aller Seiten im Verzeichnis /pages/**.
  • Statisches Servieren von Dateien für Assets im Verzeichnis /public/**.
  • 404-Fehlerseite.

Navigieren Sie zu einem zufälligen Pfad auf localhost, um die 404-Seite in Aktion zu sehen. Wenn Sie eine benutzerdefinierte 404-Seite benötigen, finden Sie in der Next.js-Dokumentation großartige Informationen.

Screenshot of the 404 page. It says 404 This page could not be found.

Dynamische Seiten

Seiten mit statischen Routen sind nützlich für die Erstellung der Homepage, der Über-uns-Seite usw. Um jedoch alle unsere Beiträge dynamisch zu erstellen, werden wir die dynamische Routenfunktion von Next.js verwenden. Zum Beispiel

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

Jede Route, wie /posts/1, /posts/abc usw., wird von /posts/[slug].js abgeglichen, und der Slug-Parameter wird als Query-Parameter an die Seite gesendet. Dies ist besonders nützlich für unsere Blogbeiträge, da wir nicht für jeden Beitrag eine eigene Datei erstellen möchten; stattdessen könnten wir den Slug dynamisch übergeben, um den entsprechenden Beitrag zu rendern.

Anatomie eines Blogs

Nachdem wir nun die grundlegenden Bausteine von Next.js verstanden haben, wollen wir die Anatomie unseres Blogs definieren.

.
├─ 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

Blog-API

Ein grundlegendes Blog-Framework benötigt zwei API-Funktionen: 

  • Eine Funktion zum Abrufen der Metadaten aller Beiträge im Verzeichnis _posts
  • Eine Funktion zum Abrufen eines einzelnen Beitrags für einen gegebenen slug mit dem vollständigen HTML und den Metadaten

Optional möchten wir auch, dass die gesamte Website-Konfiguration, die in config.yml definiert ist, in allen Komponenten verfügbar ist. Daher benötigen wir eine Funktion, die die YAML-Konfiguration in ein natives Objekt parst.

Da wir uns mit vielen Nicht-JavaScript-Dateien wie Markdown (.md), YAML (.yml) usw. beschäftigen werden, verwenden wir die raw-loader-Bibliothek, um solche Dateien als Strings zu laden, um die Verarbeitung zu erleichtern. 

npm install raw-loader --save-dev

Als Nächstes müssen wir Next.js mitteilen, dass es raw-loader verwenden soll, wenn wir .md- und .yml-Dateiformate importieren, indem wir eine Datei next.config.js im Stammverzeichnis des Projekts erstellen (weitere Informationen dazu).

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 führte Aliase für relative Imports ein, was hilft, die durch relative Pfade verursachten Import-Anweisungs-Spaghetti zu bereinigen. Um Aliase zu verwenden, erstellen Sie eine Datei jsconfig.json im Stammverzeichnis des Projekts, die den Basispfad und alle für das Projekt benötigten Modul-Aliase angibt.

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

Zum Beispiel ermöglicht uns dies, unsere Layouts einfach durch die Verwendung von zu importieren

import DefaultLayout from '@layouts/default'

Alle Beiträge abrufen

Diese Funktion liest alle Markdown-Dateien im Verzeichnis _posts, parst den Front Matter am Anfang des Beitrags mit gray-matter und gibt das Array der Metadaten für alle Beiträge zurück.

// 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;
}

Ein typischer Markdown-Beitrag sieht so aus

---
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!

Der durch --- abgegrenzte Abschnitt wird als Front Matter bezeichnet, der die Metadaten des Beitrags enthält, wie Titel, Permalink, Tags usw. Hier ist die Ausgabe

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

Stellen Sie sicher, dass Sie die gray-matter-Bibliothek zuerst über npm mit dem Befehl npm install gray-matter --save-dev installieren.

Einen einzelnen Beitrag abrufen

Für einen gegebenen Slug sucht diese Funktion die Datei im Verzeichnis _posts, parst das Markdown mit der marked-Bibliothek und gibt das Ausgabe-HTML mit Metadaten zurück.

// 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
  }
}

Beispielausgabe

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

Stellen Sie sicher, dass Sie die marked-Bibliothek zuerst über npm mit dem Befehl npm install marked --save-dev installieren.

Konfiguration

Um die Jekyll-Konfiguration für unseren Next.js-Blog wiederzuverwenden, parsen wir die YAML-Datei mit der js-yaml-Bibliothek und exportieren diese Konfiguration, damit sie in den Komponenten verwendet werden kann.

// 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)
}

Stellen Sie sicher, dass Sie js-yaml zuerst über npm mit dem Befehl npm install js-yaml --save-dev installieren.

Includes

Unser Verzeichnis _includes enthält zwei einfache React-Komponenten, <Header> und <Footer>, die in den verschiedenen Layout-Komponenten im Verzeichnis _layouts verwendet werden.

// _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

Wir haben zwei Layout-Komponenten im Verzeichnis _layouts. Eine ist <DefaultLayout>, die das Basislayout ist, auf dem alle anderen Layout-Komponenten aufgebaut werden.

// _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>
  )
}

Das zweite Layout ist die Komponente <PostLayout>, die den im <DefaultLayout> definierten Titel mit dem Beitragstitel überschreibt und den HTML des Beitrags rendert. Sie enthält auch einen Link zurück zur Homepage.

// _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 ist eine integrierte Komponente, um Elemente zum <head> der Seite hinzuzufügen. next/link ist eine integrierte Komponente, die clientseitige Übergänge zwischen den im Seitenverzeichnis definierten Routen handhabt.

Homepage

Als Teil der Index-Seite, auch bekannt als Homepage, werden wir alle Beiträge im Verzeichnis _posts auflisten. Die Liste wird den Beitragstitel und den Permalink zur einzelnen Beitragsseite enthalten. Die Index-Seite verwendet <DefaultLayout>, und wir importieren die Konfiguration auf der Homepage, um title und description an das Layout zu übergeben.

// 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 wird zur Build-Zeit aufgerufen, um Seiten vorab zu rendern, indem props an die Standardkomponente der Seite übergeben werden. Wir verwenden diese Funktion, um die Liste aller Beiträge zur Build-Zeit abzurufen und das Beitragsarchiv auf der Homepage zu rendern.

Screenshot of the homepage showing the page title, a list with two post titles, and the footer.

Beitragsseite

Diese Seite rendert den Titel und den Inhalt des Beitrags für den slug, der als Teil des context übergeben wird. Die Beitragsseite verwendet die Komponente <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
  }
}

Wenn eine Seite dynamische Routen hat, muss Next.js zur Build-Zeit alle möglichen Pfade kennen. getStaticPaths liefert die Liste der Pfade, die zur Build-Zeit als HTML gerendert werden müssen. Die Eigenschaft fallback stellt sicher, dass bei einem Besuch einer Route, die nicht in der Liste der Pfade vorhanden ist, eine 404-Seite zurückgegeben wird.

Screenshot of the blog page showing a welcome header and a hello world blue above the footer.

Produktionsreif

Fügen Sie die folgenden Befehle für build und start in package.json im Abschnitt scripts hinzu und führen Sie dann npm run build gefolgt von npm run start aus, um das statische Blog zu erstellen und den Produktionsserver zu starten.

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

Der gesamte Quellcode in diesem Artikel ist auf diesem GitHub-Repository verfügbar. Klonen Sie es gerne lokal und spielen Sie damit herum. Das Repository enthält auch einige grundlegende Platzhalter zum Anwenden von CSS auf Ihren Blog.

Verbesserungen

Der Blog ist zwar funktionsfähig, aber für die meisten durchschnittlichen Fälle vielleicht zu einfach. Es wäre schön, das Framework zu erweitern oder einen Patch einzureichen, um einige weitere Funktionen hinzuzufügen, wie z. B.:

  • Pagination (Seitennummerierung)
  • Syntax-Hervorhebung
  • Kategorien und Tags für Beiträge
  • Styling

Insgesamt scheint Next.js sehr vielversprechend für die Erstellung statischer Websites wie eines Blogs zu sein. Gepaart mit seiner Fähigkeit, statisches HTML zu exportieren, können wir eine wirklich eigenständige App ohne Server erstellen!