Membuat Blog Multi-Penulis Dengan Next.js

Diterbitkan: 2022-03-10
Ringkasan cepat Artikel ini menjelaskan bagaimana kita dapat menghubungkan berbagai jenis konten dalam aplikasi Next.js. Dengan teknik ini, kita dapat menambahkan segala jenis hubungan satu-ke-satu, satu-ke-banyak, atau bahkan banyak-ke-banyak ke proyek kami.

Pada artikel ini, kita akan membuat blog dengan Next.js yang mendukung dua atau lebih penulis. Kami akan mengatribusikan setiap posting ke penulis dan menunjukkan nama dan gambar mereka dengan posting mereka. Setiap penulis juga mendapatkan halaman profil, yang mencantumkan semua posting yang mereka sumbangkan. Ini akan terlihat seperti ini:

Di sebelah kiri: indeks blog selesai yang akan kita buat. Di sebelah kanan: halaman posting individu, yang tertaut ke halaman profil penulisnya.
Di sebelah kiri: indeks blog yang sudah jadi yang akan kita buat. Di sebelah kanan: halaman posting individu, yang tertaut ke halaman profil penulisnya. (Pratinjau besar)
Halaman profil penulis, menautkan ke semua postingan mereka.
Halaman profil penulis, menautkan ke semua postingan mereka. (Pratinjau besar)

Kami akan menyimpan semua informasi dalam file di sistem file lokal. Kedua jenis konten, posting dan penulis, akan menggunakan jenis file yang berbeda. Postingan teks-berat akan menggunakan penurunan harga, memungkinkan proses pengeditan yang lebih mudah. Karena informasi tentang penulis lebih ringan, kami akan menyimpannya di file JSON. Fungsi pembantu akan membuat membaca berbagai jenis file dan menggabungkan konten mereka lebih mudah.

Next.js memungkinkan kita membaca data dari berbagai sumber dan jenis yang berbeda dengan mudah. Berkat perutean dinamis dan next/link , kami dapat dengan cepat membangun dan menavigasi ke berbagai halaman situs kami. Kami juga mendapatkan pengoptimalan gambar secara gratis dengan paket next/image .

Dengan memilih "termasuk baterai" Next.js, kita dapat fokus pada aplikasi kita sendiri. Kami tidak perlu menghabiskan waktu untuk mengerjakan proyek baru yang berulang-ulang. Alih-alih membangun semuanya dengan tangan, kita dapat mengandalkan kerangka kerja yang teruji dan terbukti. Komunitas besar dan aktif di belakang Next.js memudahkan untuk mendapatkan bantuan jika kami mengalami masalah di sepanjang jalan.

Setelah membaca artikel ini, Anda akan dapat menambahkan banyak jenis konten ke satu proyek Next.js. Anda juga akan dapat menciptakan hubungan di antara mereka. Itu memungkinkan Anda untuk menautkan hal-hal seperti penulis dan posting, kursus dan pelajaran, atau aktor dan film.

Artikel ini mengasumsikan keakraban dasar dengan Next.js. Jika Anda belum pernah menggunakannya sebelumnya, Anda mungkin ingin membaca tentang cara menangani halaman dan mengambil data untuknya terlebih dahulu.

Kami tidak akan membahas gaya dalam artikel ini dan fokus untuk membuat semuanya berfungsi sebagai gantinya. Anda bisa mendapatkan hasilnya di GitHub. Ada juga stylesheet yang dapat Anda masukkan ke dalam proyek Anda jika Anda ingin mengikuti artikel ini. Untuk mendapatkan bingkai yang sama, termasuk navigasi, ganti pages/_app.js Anda dengan file ini.

Lebih banyak setelah melompat! Lanjutkan membaca di bawah ini

Mempersiapkan

Kami mulai dengan menyiapkan proyek baru menggunakan create-next-app dan mengubah ke direktorinya:

 $ npx create-next-app multiauthor-blog $ cd multiauthor-blog

Kita perlu membaca file penurunan harga nanti. Untuk mempermudah ini, kami juga menambahkan beberapa dependensi lagi sebelum memulai.

 multiauthor-blog$ yarn add gray-matter remark remark-html

Setelah instalasi selesai, kita dapat menjalankan skrip dev untuk memulai proyek kita:

 multiauthor-blog$ yarn dev

Kami sekarang dapat menjelajahi situs kami. Di browser Anda, buka https://localhost:3000. Anda akan melihat halaman default ditambahkan oleh create-next-app.

Halaman default dibuat dengan create-next-app.
Jika Anda melihat ini, pengaturan Anda berfungsi. (Pratinjau besar)

Sebentar lagi, kita akan membutuhkan navigasi untuk mencapai halaman kita. Kita dapat menambahkannya di pages/_app.js bahkan sebelum halaman itu ada.

 import Link from 'next/link' import '../styles/globals.css' export default function App({ Component, pageProps }) { return ( <> <header> <nav> <ul> <li> <Link href="/"> <a>Home</a> </Link> </li> <li> <Link href="/posts"> <a>Posts</a> </Link> </li> <li> <Link href="/authors"> <a>Authors</a> </Link> </li> </ul> </nav> </header> <main> <Component {...pageProps} /> </main> </> ) }

Sepanjang artikel ini, kami akan menambahkan halaman yang hilang ini yang menjadi tujuan navigasi. Mari kita tambahkan beberapa posting terlebih dahulu sehingga kita memiliki sesuatu untuk dikerjakan di halaman tinjauan blog.

Membuat Posting

Untuk menjaga konten kami terpisah dari kode, kami akan menempatkan posting kami di direktori bernama _posts/ . Untuk mempermudah penulisan dan pengeditan, kami akan membuat setiap posting sebagai file penurunan harga. Nama file setiap posting akan berfungsi sebagai slug di rute kami nanti. File _posts/hello-world.md akan dapat diakses di bawah /posts/hello-world , misalnya.

Beberapa informasi, seperti judul lengkap dan kutipan singkat, berada di frontmatter di awal file.

 --- title: "Hello World!" excerpt: "This is my first blog post." createdAt: "2021-05-03" --- Hey, how are you doing? Welcome to my blog. In this post, …

Tambahkan beberapa file lagi seperti ini agar blog tidak kosong:

 multi-author-blog/ ├─ _posts/ │ ├─ hello-world.md │ ├─ multi-author-blog-in-nextjs.md │ ├─ styling-react-with-tailwind.md │ └─ ten-hidden-gems-in-javascript.md └─ pages/ └─ …

Anda dapat menambahkan posting Anda sendiri atau mengambil contoh posting ini dari repositori GitHub.

Daftar Semua Posting

Sekarang kita memiliki beberapa posting, kita perlu cara untuk memasukkannya ke blog kita. Mari kita mulai dengan menambahkan halaman yang mencantumkan semuanya, yang berfungsi sebagai indeks blog kita.

Di Next.js, file yang dibuat di bawah pages/posts/index.js akan dapat diakses sebagai /posts di situs kami. File harus mengekspor fungsi yang akan berfungsi sebagai isi halaman itu. Versi pertamanya terlihat seperti ini:

 export default function Posts() { return ( <div className="posts"> <h1>Posts</h1> {/* TODO: render posts */} </div> ) }

Kami tidak terlalu jauh karena kami belum memiliki cara untuk membaca file penurunan harga. Kami sudah dapat menavigasi ke https://localhost:3000/posts, tetapi kami hanya melihat judulnya.

Halaman kosong dengan judul yang bertuliskan 'Postingan'.
Kami dapat mengakses halaman kami dan dapat mulai mengisinya dengan kehidupan. (Pratinjau besar)

Kami sekarang membutuhkan cara untuk menempatkan posting kami di sana. Next.js menggunakan fungsi yang disebut getStaticProps() untuk meneruskan data ke komponen halaman. Fungsi meneruskan props di objek yang dikembalikan ke komponen sebagai props.

Dari getStaticProps() , kita akan meneruskan postingan ke komponen sebagai prop yang disebut posts . Kami akan membuat hardcode dua pos placeholder di langkah pertama ini. Dengan memulai cara ini, kami menentukan format apa yang nantinya kami inginkan untuk menerima kiriman sebenarnya. Jika fungsi pembantu mengembalikannya dalam format ini, kami dapat beralih ke sana tanpa mengubah komponen.

Ikhtisar postingan tidak akan menampilkan teks lengkap postingan. Untuk halaman ini, cukup judul, kutipan, permalink, dan tanggal setiap posting.

 export default function Posts() { … } +export function getStaticProps() { + return { + props: { + posts: [ + { + title: "My first post", + createdAt: "2021-05-01", + excerpt: "A short excerpt summarizing the post.", + permalink: "/posts/my-first-post", + slug: "my-first-post", + }, { + title: "My second post", + createdAt: "2021-05-04", + excerpt: "Another summary that is short.", + permalink: "/posts/my-second-post", + slug: "my-second-post", + } + ] + } + } +}

Untuk memeriksa koneksi, kita dapat mengambil posting dari props dan menunjukkannya di komponen Posts . Kami akan menyertakan judul, tanggal pembuatan, kutipan, dan tautan ke pos. Untuk saat ini, tautan itu belum mengarah ke mana pun.

 +import Link from 'next/link' -export default function Posts() { +export default function Posts({ posts }) { return ( <div className="posts"> <h1>Posts</h1> - {/* TODO: render posts */} + {posts.map(post => { + const prettyDate = new Date(post.createdAt).toLocaleString('en-US', { + month: 'short', + day: '2-digit', + year: 'numeric', + }) + + return ( + <article key={post.slug}> + <h2> + <Link href={post.permalink}> + <a>{post.title}</a> + </Link> + </h2> + + <time dateTime={post.createdAt}>{prettyDate}</time> + + <p>{post.excerpt}</p> + + <Link href={post.permalink}> + <a>Read more →</a> + </Link> + </article> + ) + })} </div> ) } export function getStaticProps() { … }

Setelah memuat ulang halaman di browser, sekarang menunjukkan dua posting ini:

Daftar dua pos placeholder kami.
Koneksi berfungsi. Sekarang kita dapat bekerja menempatkan posting nyata di sini. (Pratinjau besar)

Kami tidak ingin membuat hardcode semua posting blog kami di getStaticProps() selamanya. Lagi pula, itu sebabnya kami membuat semua file ini di direktori _posts/ sebelumnya. Kami sekarang membutuhkan cara untuk membaca file-file itu dan meneruskan kontennya ke komponen halaman.

Ada beberapa cara yang bisa kita lakukan. Kita bisa membaca file langsung di getStaticProps() . Karena fungsi ini berjalan di server dan bukan di klien, kami memiliki akses ke modul Node.js asli seperti fs di dalamnya. Kita bisa membaca, mengubah, dan bahkan memanipulasi file lokal di file yang sama dengan komponen halaman yang kita simpan.

Agar file tetap pendek dan fokus pada satu tugas, kita akan memindahkan fungsionalitas itu ke file terpisah. Dengan begitu, komponen Posts hanya perlu menampilkan data, tanpa harus membaca data itu sendiri. Ini menambahkan beberapa pemisahan dan organisasi ke proyek kami.

Secara konvensi, kita akan meletakkan fungsi membaca data dalam file bernama lib/api.js . File itu akan menampung semua fungsi yang mengambil konten kami untuk komponen yang menampilkannya.

Untuk halaman ikhtisar posting, kami menginginkan fungsi yang membaca, memproses, dan mengembalikan semua posting. Kami akan menyebutnya getAllPosts() . Di dalamnya, pertama-tama kita menggunakan path.join() untuk membangun path ke direktori _posts/ . Kami kemudian menggunakan fs.readdirSync() untuk membaca direktori itu, yang memberi kami nama semua file di dalamnya. Memetakan nama-nama ini, kami kemudian membaca setiap file secara bergantian.

 import fs from 'fs' import path from 'path' export function getAllPosts() { const postsDirectory = path.join(process.cwd(), '_posts') const filenames = fs.readdirSync(postsDirectory) return filenames.map(filename => { const file = fs.readFileSync(path.join(process.cwd(), '_posts', filename), 'utf8') // TODO: transform and return file }) }

Setelah membaca file, kami mendapatkan isinya sebagai string panjang. Untuk memisahkan frontmatter dari teks postingan, kami menjalankan string itu melalui gray-matter . Kami juga akan mengambil slug setiap posting dengan menghapus .md dari akhir nama filenya. Kami membutuhkan siput itu untuk membangun URL dari mana pos akan dapat diakses nanti. Karena kita tidak memerlukan badan penurunan harga dari posting untuk fungsi ini, kita dapat mengabaikan konten yang tersisa.

 import fs from 'fs' import path from 'path' +import matter from 'gray-matter' export function getAllPosts() { const postsDirectory = path.join(process.cwd(), '_posts') const filenames = fs.readdirSync(postsDirectory) return filenames.map(filename => { const file = fs.readFileSync(path.join(process.cwd(), '_posts', filename), 'utf8') - // TODO: transform and return file + // get frontmatter + const { data } = matter(file) + + // get slug from filename + const slug = filename.replace(/\.md$/, '') + + // return combined frontmatter and slug; build permalink + return { + ...data, + slug, + permalink: `/posts/${slug}`, + } }) }

Perhatikan bagaimana kita menyebarkan ...data ke objek yang dikembalikan di sini. Itu memungkinkan kita mengakses nilai dari frontmatternya sebagai {post.title} alih-alih {post.data.title} nanti.

Kembali ke halaman ikhtisar posting kami, kami sekarang dapat mengganti posting placeholder dengan fungsi baru ini.

 +import { getAllPosts } from '../../lib/api' export default function Posts({ posts }) { … } export function getStaticProps() { return { props: { - posts: [ - { - title: "My first post", - createdAt: "2021-05-01", - excerpt: "A short excerpt summarizing the post.", - permalink: "/posts/my-first-post", - slug: "my-first-post", - }, { - title: "My second post", - createdAt: "2021-05-04", - excerpt: "Another summary that is short.", - permalink: "/posts/my-second-post", - slug: "my-second-post", - } - ] + posts: getAllPosts(), } } }

Setelah memuat ulang browser, kami sekarang melihat posting asli kami alih-alih placeholder yang kami miliki sebelumnya.

Daftar posting blog kami yang sebenarnya.
Berkat fungsi helper, halaman ini sekarang menampilkan postingan kita yang sebenarnya. (Pratinjau besar)

Menambahkan Halaman Posting Individu

Tautan yang kami tambahkan ke setiap pos belum mengarah ke mana pun. Belum ada halaman yang menanggapi URL seperti /posts/hello-world . Dengan perutean dinamis, kita dapat menambahkan halaman yang cocok dengan semua jalur seperti ini.

File yang dibuat sebagai pages/posts/[slug].js akan cocok dengan semua URL yang terlihat seperti /posts/abc . Nilai yang muncul sebagai ganti [slug] di URL akan tersedia untuk halaman sebagai parameter kueri. Kita dapat menggunakannya di halaman terkait getStaticProps() sebagai params.slug untuk memanggil fungsi pembantu.

Sebagai lawan dari getAllPosts() , kita akan memanggil fungsi pembantu itu getPostBySlug(slug) . Alih-alih semua posting, itu akan mengembalikan satu posting yang cocok dengan slug yang kami lewati. Pada halaman posting, kami juga perlu menunjukkan konten penurunan harga file yang mendasarinya.

Halaman untuk masing-masing posting terlihat seperti halaman untuk ikhtisar posting. Alih-alih meneruskan posts ke halaman di getStaticProps() , kami hanya meneruskan satu post . Mari kita lakukan pengaturan umum terlebih dahulu sebelum kita melihat cara mengubah badan Penurunan harga postingan menjadi HTML yang dapat digunakan. Kami akan melewati pos placeholder di sini, menggunakan fungsi pembantu yang akan kami tambahkan di langkah berikutnya segera.

 import { getPostBySlug } from '../../lib/api' export default function Post({ post }) { const prettyDate = new Date(post.createdAt).toLocaleString('en-US', { month: 'short', day: '2-digit', year: 'numeric', }) return ( <div className="post"> <h1>{post.title}</h1> <time dateTime={post.createdAt}>{prettyDate}</time> {/* TODO: render body */} </div> ) } export function getStaticProps({ params }) { return { props: { post: getPostBySlug(params.slug), }, } }

Kita sekarang harus menambahkan fungsi getPostBySlug(slug) ke file pembantu kita lib/api.js . Ini seperti getAllPosts() , dengan beberapa perbedaan mencolok. Karena kita bisa mendapatkan nama file postingan dari slug, kita tidak perlu membaca seluruh direktori terlebih dahulu. Jika slugnya adalah 'hello-world' , kita akan membaca file bernama _posts/hello-world.md . Jika file tersebut tidak ada, Next.js akan menampilkan halaman error 404.

Perbedaan lain dari getAllPosts() adalah kali ini, kita juga perlu membaca konten Markdown postingan. Kami dapat mengembalikannya sebagai HTML siap-render alih-alih Penurunan Harga mentah dengan memprosesnya dengan remark terlebih dahulu.

 import fs from 'fs' import path from 'path' import matter from 'gray-matter' +import remark from 'remark' +import html from 'remark-html' export function getAllPosts() { … } +export function getPostBySlug(slug) { + const file = fs.readFileSync(path.join(process.cwd(), '_posts', `${slug}.md`), 'utf8') + + const { + content, + data, + } = matter(file) + + const body = remark().use(html).processSync(content).toString() + + return { + ...data, + body, + } +}

Secara teori, kita bisa menggunakan fungsi getAllPosts() di dalam getPostBySlug(slug) . Pertama-tama kami akan mendapatkan semua posting dengannya, yang kemudian kami dapat mencari yang cocok dengan slug yang diberikan. Itu berarti kita akan selalu perlu membaca semua posting sebelum kita bisa mendapatkan satu pun, yang merupakan pekerjaan yang tidak perlu. getAllPosts() juga tidak mengembalikan konten penurunan harga posting. Kami dapat memperbaruinya untuk melakukan itu, dalam hal ini ia akan melakukan lebih banyak pekerjaan daripada yang dibutuhkan saat ini.

Karena kedua fungsi helper melakukan hal yang berbeda, kita akan memisahkannya. Dengan begitu, kita dapat memfokuskan fungsi-fungsi tersebut secara tepat dan hanya pada tugas yang kita perlukan untuk masing-masing fungsi tersebut.

Halaman yang menggunakan perutean dinamis dapat menyediakan getStaticPaths() di samping getStaticProps() . Fungsi ini memberi tahu Next.js nilai dari segmen jalur dinamis untuk membuat halaman. Kami dapat menyediakannya dengan menggunakan getAllPosts() dan mengembalikan daftar objek yang mendefinisikan slug setiap posting.

 -import { getPostBySlug } from '../../lib/api' +import { getAllPosts, getPostBySlug } from '../../lib/api' export default function Post({ post }) { … } export function getStaticProps({ params }) { … } +export function getStaticPaths() { + return { + fallback: false, + paths: getAllPosts().map(post => ({ + params: { + slug: post.slug, + }, + })), + } +}

Karena kami mengurai konten penurunan harga di getPostBySlug(slug) , kami dapat merendernya di halaman sekarang. Kita perlu menggunakan dangerouslySetInnerHTML untuk langkah ini sehingga Next.js dapat merender HTML di belakang post.body . Terlepas dari namanya, aman untuk menggunakan properti dalam skenario ini. Karena kami memiliki kendali penuh atas kiriman kami, kecil kemungkinan mereka akan menyuntikkan skrip yang tidak aman.

 import { getAllPosts, getPostBySlug } from '../../lib/api' export default function Post({ post }) { const prettyDate = new Date(post.createdAt).toLocaleString('en-US', { month: 'short', day: '2-digit', year: 'numeric', }) return ( <div className="post"> <h1>{post.title}</h1> <time dateTime={post.createdAt}>{prettyDate}</time> - {/* TODO: render body */} + <div dangerouslySetInnerHTML={{ __html: post.body }} /> </div> ) } export function getStaticProps({ params }) { … } export function getStaticPaths() { … }

Jika kita mengikuti salah satu tautan dari ikhtisar posting, sekarang kita sampai ke halaman posting itu sendiri.

Halaman posting individu.
Kami bisa menunjukkan isi postingan tersebut, tapi belum tahu siapa yang menulisnya. (Pratinjau besar)

Menambahkan Penulis

Sekarang setelah kita memiliki posting yang terhubung, kita perlu mengulangi langkah yang sama untuk penulis kita. Kali ini, kita akan menggunakan JSON sebagai ganti Markdown untuk mendeskripsikannya. Kami dapat mencampur berbagai jenis file dalam proyek yang sama seperti ini kapan pun itu masuk akal. Fungsi pembantu yang kami gunakan untuk membaca file menangani perbedaan apa pun untuk kami. Halaman dapat menggunakan fungsi ini tanpa mengetahui format apa yang kami gunakan untuk menyimpan konten kami.

Pertama, buat direktori bernama _authors/ dan tambahkan beberapa file penulis ke dalamnya. Seperti yang kami lakukan dengan posting, beri nama file dengan slug masing-masing penulis. Kami akan menggunakannya untuk mencari penulis nanti. Di setiap file, kami menentukan nama lengkap penulis dalam objek JSON.

 { "name": "Adrian Webber" }

Untuk saat ini, memiliki dua penulis dalam proyek kami sudah cukup.

Untuk memberi mereka lebih banyak kepribadian, mari tambahkan juga gambar profil untuk setiap penulis. Kami akan meletakkan file statis tersebut di direktori public/ . Dengan menamai file dengan slug yang sama, kita dapat menghubungkannya menggunakan konvensi tersirat saja. Kita dapat menambahkan jalur gambar ke file JSON masing-masing penulis untuk menautkan keduanya. Dengan menamai semua file dengan slug, kita dapat mengatur koneksi ini tanpa harus menuliskannya. Objek JSON hanya perlu menyimpan informasi yang tidak dapat kita buat dengan kode.

Setelah selesai, direktori proyek Anda akan terlihat seperti ini.

 multi-author-blog/ ├─ _authors/ │ ├─ adrian-webber.json │ └─ megan-carter.json ├─ _posts/ │ └─ … ├─ pages/ │ └─ … └─ public/ ├─ adrian-webber.jpg └─ megan-carter.jpg

Sama seperti posting, kita sekarang membutuhkan fungsi pembantu untuk membaca semua penulis dan mendapatkan penulis individu. Fungsi baru getAllAuthors() dan getAuthorBySlug(slug) juga masuk lib/api.js . Mereka melakukan hampir persis sama dengan rekan-rekan mereka di pos. Karena kami menggunakan JSON untuk mendeskripsikan penulis, kami tidak perlu menguraikan penurunan harga apa pun dengan remark di sini. Kami juga tidak membutuhkan gray-matter untuk mengurai frontmatter. Sebagai gantinya, kita dapat menggunakan JSON.parse() untuk membaca konten teks file kita menjadi objek.

 const contents = fs.readFileSync(somePath, 'utf8') // ⇒ looks like an object, but is a string // eg '{ "name": "John Doe" }' const json = JSON.parse(contents) // ⇒ a real JavaScript object we can do things with // eg { name: "John Doe" }

Dengan pengetahuan itu, fungsi pembantu kami terlihat seperti ini:

 export function getAllPosts() { … } export function getPostBySlug(slug) { … } +export function getAllAuthors() { + const authorsDirectory = path.join(process.cwd(), '_authors') + const filenames = fs.readdirSync(authorsDirectory) + + return filenames.map(filename => { + const file = fs.readFileSync(path.join(process.cwd(), '_authors', filename), 'utf8') + + // get data + const data = JSON.parse(file) + + // get slug from filename + const slug = filename.replace(/\.json/, '') + + // return combined frontmatter and slug; build permalink + return { + ...data, + slug, + permalink: `/authors/${slug}`, + profilePictureUrl: `${slug}.jpg`, + } + }) +} + +export function getAuthorBySlug(slug) { + const file = fs.readFileSync(path.join(process.cwd(), '_authors', `${slug}.json`), 'utf8') + + const data = JSON.parse(file) + + return { + ...data, + permalink: `/authors/${slug}`, + profilePictureUrl: `/${slug}.jpg`, + slug, + } +}

Dengan cara membaca penulis ke dalam aplikasi kita, sekarang kita dapat menambahkan halaman yang mencantumkan semuanya. Membuat halaman baru di bawah pages/authors/index.js memberi kita halaman /authors di situs kita.

Fungsi pembantu mengurus membaca file untuk kita. Komponen halaman ini tidak perlu diketahui penulis adalah file JSON di sistem file. Itu dapat menggunakan getAllAuthors() tanpa mengetahui di mana atau bagaimana ia mendapatkan datanya. Format tidak menjadi masalah selama fungsi pembantu kami mengembalikan datanya dalam format yang dapat kami gunakan. Abstraksi seperti ini memungkinkan kita mencampur berbagai jenis konten di seluruh aplikasi kita.

Halaman indeks untuk penulis sangat mirip dengan halaman untuk posting. Kami mendapatkan semua penulis di getStaticProps() , yang meneruskannya ke komponen Authors . Komponen itu memetakan setiap penulis dan mencantumkan beberapa informasi tentang mereka. Kami tidak perlu membuat tautan atau URL lain dari siput. Fungsi pembantu sudah mengembalikan penulis dalam format yang dapat digunakan.

 import Image from 'next/image' import Link from 'next/link' import { getAllAuthors } from '../../lib/api/authors' export default function Authors({ authors }) { return ( <div className="authors"> <h1>Authors</h1> {authors.map(author => ( <div key={author.slug}> <h2> <Link href={author.permalink}> <a>{author.name}</a> </Link> </h2> <Image alt={author.name} src={author.profilePictureUrl} height="40" width="40" /> <Link href={author.permalink}> <a>Go to profile →</a> </Link> </div> ))} </div> ) } export function getStaticProps() { return { props: { authors: getAllAuthors(), }, } }

Jika kami mengunjungi /authors di situs kami, kami melihat daftar semua penulis dengan nama dan gambar mereka.

Daftar penulis.
Kami dapat membuat daftar semua penulis, tetapi tidak tahu berapa banyak artikel yang mereka sumbangkan. (Pratinjau besar)

Tautan ke profil penulis belum mengarah ke mana pun. Untuk menambahkan halaman profil, kami membuat file di bawah pages/authors/[slug].js . Karena penulis tidak memiliki konten teks apa pun, yang dapat kami tambahkan untuk saat ini hanyalah nama dan gambar profil mereka. Kami juga membutuhkan getStaticPaths() lain untuk memberi tahu Next.js untuk apa slug membuat halaman.

 import Image from 'next/image' import { getAllAuthors, getAuthorBySlug } from '../../lib/api' export default function Author({ author }) { return ( <div className="author"> <h1>{author.name}</h1> <Image alt={author.name} src={author.profilePictureUrl} height="80" width="80" /> </div> ) } export function getStaticProps({ params }) { return { props: { author: getAuthorBySlug(params.slug), }, } } export function getStaticPaths() { return { fallback: false, paths: getAllAuthors().map(author => ({ params: { slug: author.slug, }, })), } }

Dengan ini, kami sekarang memiliki halaman profil penulis dasar yang sangat ringan tentang informasi.

Halaman profil penulis, menunjukkan nama dan foto kepala mereka.
Halaman profil penulis sebagian besar kosong sekarang. (Pratinjau besar)

Pada titik ini, penulis dan posting belum terhubung. Kami akan membangun jembatan itu selanjutnya sehingga kami dapat menambahkan daftar posting setiap penulis ke halaman profil mereka.

Menghubungkan Posting Dan Penulis

Untuk menghubungkan dua bagian konten, kita perlu merujuk satu sama lain. Karena kami sudah mengidentifikasi posting dan penulis berdasarkan siputnya, kami akan merujuk mereka dengan itu. Kita bisa menambahkan penulis ke postingan dan postingan ke penulis, tapi satu arah sudah cukup untuk menautkannya. Karena kita ingin mengatribusikan postingan ke penulis, kita akan menambahkan slug penulis ke frontmatter setiap postingan.

 --- title: "Hello World!" excerpt: "This is my first blog post." createdAt: "2021-05-03" +author: adrian-webber --- Hey, how are you doing? Welcome to my blog. In this post, …

Jika kita tetap seperti itu, menjalankan posting melalui gray-matter menambahkan bidang penulis ke posting sebagai string:

 const post = getPostBySlug("hello-world") const author = post.author console.log(author) // "adrian-webber"

Untuk mendapatkan objek yang mewakili penulis, kita dapat menggunakan slug itu dan memanggil getAuthorBySlug(slug) dengannya.

 const post = getPostBySlug("hello-world") -const author = post.author +const author = getAuthorBySlug(post.author) console.log(author) // { // name: "Adrian Webber", // slug: "adrian-webber", // profilePictureUrl: "/adrian-webber.jpg", // permalink: "/authors/adrian-webber" // }

Untuk menambahkan penulis ke halaman satu posting, kita perlu memanggil getAuthorBySlug(slug) sekali di getStaticProps() .

 +import Image from 'next/image' +import Link from 'next/link' -import { getPostBySlug } from '../../lib/api' +import { getAuthorBySlug, getPostBySlug } from '../../lib/api' export default function Post({ post }) { const prettyDate = new Date(post.createdAt).toLocaleString('en-US', { month: 'short', day: '2-digit', year: 'numeric', }) return ( <div className="post"> <h1>{post.title}</h1> <time dateTime={post.createdAt}>{prettyDate}</time> + <div> + <Image alt={post.author.name} src={post.author.profilePictureUrl} height="40" width="40" /> + + <Link href={post.author.permalink}> + <a> + {post.author.name} + </a> + </Link> + </div> <div dangerouslySetInnerHTML={{ __html: post.body }}> </div> ) } export function getStaticProps({ params }) { + const post = getPostBySlug(params.slug) return { props: { - post: getPostBySlug(params.slug), + post: { + ...post, + author: getAuthorBySlug(post.author), + }, }, } }

Perhatikan bagaimana kita menyebarkan ...post ke objek yang juga disebut post di getStaticProps() . Dengan menempatkan author setelah baris itu, kami akhirnya mengganti versi string penulis dengan objek lengkapnya. Itu memungkinkan kita mengakses properti penulis melalui post.author.name di komponen Post .

Dengan perubahan itu, kami sekarang mendapatkan tautan ke halaman profil penulis, lengkap dengan nama dan gambar mereka, di halaman posting.

Halaman posting yang sudah selesai, yang sekarang menyertakan nama penulis dan headshot.
Postingan sekarang dikaitkan dengan benar kepada penulis. (Pratinjau besar)

Menambahkan penulis ke halaman ikhtisar posting memerlukan perubahan serupa. Alih-alih memanggil getAuthorBySlug(slug) sekali, kita perlu memetakan semua posting dan memanggilnya untuk masing-masing posting.

 +import Image from 'next/image' +import Link from 'next/link' -import { getAllPosts } from '../../lib/api' +import { getAllPosts, getAuthorBySlug } from '../../lib/api' export default function Posts({ posts }) { return ( <div className="posts"> <h1>Posts</h1> {posts.map(post => { const prettyDate = new Date(post.createdAt).toLocaleString('en-US', { month: 'short', day: '2-digit', year: 'numeric', }) return ( <article key={post.slug}> <h2> <Link href={post.permalink}> <a>{post.title}</a> </Link> </h2> <time dateTime={post.createdAt}>{prettyDate}</time> + <div> + <Image alt={post.author.name} src={post.author.profilePictureUrl} height="40" width="40" /> + + <span>{post.author.name}</span> + </div> <p>{post.excerpt}</p> <Link href={post.permalink}> <a>Read more →</a> </Link> </article> ) })} </div> ) } export function getStaticProps() { return { props: { - posts: getAllPosts(), + posts: getAllPosts().map(post => ({ + ...post, + author: getAuthorBySlug(post.author), + })), } } }

Itu menambahkan penulis ke setiap posting di ikhtisar posting:

Daftar posting blog, termasuk nama dan foto kepala penulisnya.
Ini terlihat seperti blog sungguhan sekarang. (Pratinjau besar)

Kami tidak perlu menambahkan daftar posting penulis ke file JSON mereka. Di halaman profil mereka, pertama-tama kita mendapatkan semua postingan dengan getAllPosts() . Kami kemudian dapat memfilter daftar lengkap untuk yang dikaitkan dengan penulis ini.

 import Image from 'next/image' +import Link from 'next/link' -import { getAllAuthors, getAuthorBySlug } from '../../lib/api' +import { getAllAuthors, getAllPosts, getAuthorBySlug } from '../../lib/api' export default function Author({ author }) { return ( <div className="author"> <h1>{author.name}</h1> <Image alt={author.name} src={author.profilePictureUrl} height="40" width="40" /> + <h2>Posts</h2> + + <ul> + {author.posts.map(post => ( + <li> + <Link href={post.permalink}> + <a> + {post.title} + </a> + </Link> + </li> + ))} + </ul> </div> ) } export function getStaticProps({ params }) { const author = getAuthorBySlug(params.slug) return { props: { - author: getAuthorBySlug(params.slug), + author: { + ...author, + posts: getAllPosts().filter(post => post.author === author.slug), + }, }, } } export function getStaticPaths() { … }

Ini memberi kita daftar artikel di halaman profil setiap penulis.

Halaman profil penulis, sekarang termasuk daftar tautan ke posting mereka.
Kami sekarang dapat membuat daftar dan menautkan ke setiap posting penulis. (Pratinjau besar)

Pada halaman ikhtisar penulis, kami hanya akan menambahkan berapa banyak posting yang telah mereka tulis agar tidak mengacaukan antarmuka.

 import Image from 'next/image' import Link from 'next/link' -import { getAllAuthors } from '../../lib/api' +import { getAllAuthors, getAllPosts } from '../../lib/api' export default function Authors({ authors }) { return ( <div className="authors"> <h1>Authors</h1> {authors.map(author => ( <div key={author.slug}> <h2> <Link href={author.permalink}> <a> {author.name} </a> </Link> </h2> <Image alt={author.name} src={author.profilePictureUrl} height="40" width="40" /> + <p>{author.posts.length} post(s)</p> <Link href={author.permalink}> <a>Go to profile →</a> </Link> </div> ))} </div> ) } export function getStaticProps() { return { props: { - authors: getAllAuthors(), + authors: getAllAuthors().map(author => ({ + ...author, + posts: getAllPosts().filter(post => post.author === author.slug), + })), } } }

Dengan itu, halaman ikhtisar Penulis menunjukkan berapa banyak posting yang telah disumbangkan oleh masing-masing penulis.

Daftar penulis dengan jumlah posting mereka.
Kami sekarang dapat menempatkan jumlah posting kontribusi mereka dengan entri masing-masing penulis. (Pratinjau besar)

Dan itu saja! Posting dan penulis benar-benar terhubung sekarang. Kita bisa mendapatkan dari posting ke halaman profil penulis, dan dari sana ke posting mereka yang lain.

Ringkasan Dan Pandangan

Dalam artikel ini, kami menghubungkan dua jenis konten terkait melalui siput uniknya. Mendefinisikan hubungan dari posting ke penulis memungkinkan berbagai skenario. Kami sekarang dapat menunjukkan penulis di setiap posting dan mencantumkan posting mereka di halaman profil mereka.

Dengan teknik ini, kita dapat menambahkan banyak jenis hubungan lainnya. Setiap posting mungkin memiliki resensi di atas seorang penulis. Kita bisa mengaturnya dengan menambahkan kolom reviewer ke frontmatter postingan.

 --- title: "Hello World!" excerpt: "This is my first blog post." createdAt: "2021-05-03" author: adrian-webber +reviewer: megan-carter --- Hey, how are you doing? Welcome to my blog. In this post, …

Pada sistem file, peninjau adalah penulis lain dari direktori _authors/ . Kita dapat menggunakan getAuthorBySlug(slug) untuk mendapatkan informasi mereka juga.

 export function getStaticProps({ params }) { const post = getPostBySlug(params.slug) return { props: { post: { ...post, author: getAuthorBySlug(post.author), + reviewer: getAuthorBySlug(post.reviewer), }, }, } }

Kami bahkan dapat mendukung rekan penulis dengan menyebutkan dua atau lebih penulis pada postingan, bukan hanya satu orang.

 --- title: "Hello World!" excerpt: "This is my first blog post." createdAt: "2021-05-03" -author: adrian-webber +authors: + - adrian-webber + - megan-carter --- Hey, how are you doing? Welcome to my blog. In this post, …

Dalam skenario ini, kami tidak dapat lagi mencari satu penulis di getStaticProps() . Sebagai gantinya, kami akan memetakan susunan penulis ini untuk mendapatkan semuanya.

 export function getStaticProps({ params }) { const post = getPostBySlug(params.slug) return { props: { post: { ...post, - author: getAuthorBySlug(post.author), + authors: post.authors.map(getAuthorBySlug), }, }, } }

Kami juga dapat menghasilkan jenis skenario lain dengan teknik ini. Ini memungkinkan segala jenis hubungan satu-ke-satu, satu-ke-banyak, atau bahkan banyak-ke-banyak. Jika proyek Anda juga menampilkan buletin dan studi kasus, Anda juga dapat menambahkan penulis ke masing-masingnya.

Di situs tentang alam semesta Marvel, kita dapat menghubungkan karakter dan film tempat mereka muncul. Dalam olahraga, kita dapat menghubungkan pemain dan tim yang mereka mainkan saat ini.

Karena fungsi pembantu menyembunyikan sumber data, konten dapat berasal dari sistem yang berbeda. Kami dapat membaca artikel dari sistem file, komentar dari API, dan menggabungkannya ke dalam kode kami. Jika beberapa bagian konten berhubungan dengan jenis konten lain, kami dapat menghubungkannya dengan pola ini.

Sumber Daya Lebih Lanjut

Next.js menawarkan lebih banyak latar belakang tentang fungsi yang kami gunakan di halaman mereka tentang Pengambilan Data. Ini mencakup tautan ke proyek sampel yang mengambil data dari berbagai jenis sumber.

Jika Anda ingin mengambil proyek awal ini lebih jauh, lihat artikel ini:

  • Membangun Klon Situs Web Trik CSS dengan Strapi dan Next.js
    Ganti file pada sistem file lokal dengan backend bertenaga Strapi.
  • Membandingkan Metode Penataan Gaya di Next.js
    Jelajahi berbagai cara menulis CSS khusus untuk mengubah gaya pemula ini.
  • Penurunan harga/MDX dengan Next.js
    Tambahkan MDX ke proyek Anda sehingga Anda dapat menggunakan komponen JSX dan React di Markdown Anda.