Membuat Blog Multi-Penulis Dengan Next.js

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:

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 dengan file ini .

Mendirikan

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 dev untuk memulai proyek kita:

 multiauthor-blog$ yarn dev

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

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. Pertama-tama mari kita tambahkan beberapa posting sehingga kita memiliki sesuatu untuk dikerjakan di halaman ikhtisar 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 All

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 http://localhost:3000/posts , tetapi kami hanya melihat judulnya.

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, kita menentukan format apa yang nantinya kita inginkan untuk menerima posting sebenarnya. Jika fungsi helper mengembalikannya dalam format ini, kita 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 menampilkannya 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:

Kami tidak ingin membuat hardcode semua posting blog kami di getStaticProps() selamanya. Lagi pula, itu sebabnya kami membuat semua file ini di _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 dapat membaca, mengubah, dan bahkan memanipulasi file lokal dalam 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, Posts hanya perlu menampilkan data, tanpa juga 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 kita 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 posting, 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 Markdown 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.

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, kita 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) . Kami pertama-tama akan mendapatkan semua posting dengannya, yang kemudian dapat kami cari 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 pembantu melakukan hal yang berbeda, kita akan memisahkannya. Dengan begitu, kita dapat memfokuskan fungsi-fungsi tersebut secara tepat dan hanya tugas yang kita perlukan untuk masing-masing fungsi tersebut.

Halaman yang menggunakan perutean dinamis dapat menyediakan getStaticPaths() 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 belakang post.body . Terlepas dari namanya, aman untuk menggunakan properti dalam skenario ini. Karena kami memiliki kendali penuh atas postingan 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.

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 kami 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 bisa 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 sini. Kita juga tidak perlu 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 /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 masalah selama fungsi pembantu kami mengembalikan data mereka 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.

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() 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.

Pada titik ini, penulis dan postingan belum terhubung. Kami akan membangun jembatan itu selanjutnya sehingga kami dapat menambahkan daftar posting masing-masing 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 merujuknya dengan itu. Kita dapat menambahkan penulis ke postingan dan postingan ke penulis, tetapi 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 pos melalui gray-matter menambahkan bidang penulis ke pos 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.

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:

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.

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 setiap penulis.

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 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 penulis bersama 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 di Data Fetching . 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:

June 25, 2021

codeorayo

Ampuh! Ini rahasia mengembangkan aplikasi secara instan, tinggal download dan kembangkan. Gabung sekarang juga! Premium Membership [PRIVATE] https://premium.codeorayo.com

Leave a Reply

Your email address will not be published. Required fields are marked *