Next.js ile Çok Yazarlı Bir Blog Oluşturma

Yayınlanan: 2022-03-10
Hızlı özet ↬ Bu makale, bir Next.js uygulamasında farklı içerik türlerini nasıl bağlayabileceğimizi açıklamaktadır. Bu teknikle projelerimize her türlü bire bir, bire çoğa, hatta çoktan çoğa ilişki ekleyebiliriz.

Bu yazımızda Next.js ile iki veya daha fazla yazarı destekleyen bir blog oluşturacağız. Her gönderiyi bir yazara atfedeceğiz ve gönderileriyle birlikte adını ve resmini göstereceğiz. Her yazara, katkıda bulundukları tüm gönderileri listeleyen bir profil sayfası da verilir. Bunun gibi bir şey görünecek:

Solda: oluşturacağımız bitmiş blog dizini. Sağda: yazarının profil sayfasına bağlantı veren bireysel bir gönderinin sayfası.
Solda: oluşturacağımız bitmiş blog dizini. Sağda: yazarının profil sayfasına bağlantı veren bireysel bir gönderinin sayfası. (Büyük önizleme)
Bir yazarın tüm gönderilerine bağlantı veren profil sayfası.
Bir yazarın tüm gönderilerine bağlantı veren profil sayfası.(Geniş önizleme)

Tüm bilgileri yerel dosya sistemindeki dosyalarda tutacağız. İki tür içerik, gönderiler ve yazarlar, farklı dosya türlerini kullanır. Metin ağırlıklı gönderiler, daha kolay bir düzenleme işlemine izin veren Markdown kullanacak. Yazarlar hakkındaki bilgiler daha hafif olduğu için bunu JSON dosyalarında tutacağız. Yardımcı işlevler, farklı dosya türlerinin okunmasını ve içeriklerinin birleştirilmesini kolaylaştıracaktır.

Next.js, farklı kaynaklardan ve farklı türlerden verileri zahmetsizce okumamızı sağlar. Dinamik yönlendirmesi ve next/link sayesinde sitemizin çeşitli sayfalarını hızlı bir şekilde oluşturabilir ve gezinebiliriz. Ayrıca next/image paketi ile ücretsiz olarak görüntü optimizasyonu elde ediyoruz.

"Piller dahil" Next.js'yi seçerek, uygulamamızın kendisine odaklanabiliriz. Yeni projelerin sıklıkla birlikte geldiği tekrar eden temel çalışmalara zaman harcamak zorunda değiliz. Her şeyi elle oluşturmak yerine, test edilmiş ve kanıtlanmış çerçeveye güvenebiliriz. Next.js'nin arkasındaki geniş ve aktif topluluk, yol boyunca sorunlarla karşılaşırsak yardım almayı kolaylaştırır.

Bu makaleyi okuduktan sonra, tek bir Next.js projesine birçok türde içerik ekleyebileceksiniz. Aralarında ilişkiler de kurabileceksiniz. Bu, yazarlar ve gönderiler, kurslar ve dersler veya aktörler ve filmler gibi şeyleri bağlamanıza olanak tanır.

Bu makalede, Next.js ile temel düzeyde aşina olunduğu varsayılmaktadır. Daha önce kullanmadıysanız, sayfaları nasıl işlediğini ve önce onlar için verileri nasıl getirdiğini okumak isteyebilirsiniz.

Bu makalede şekillendirmeyi ele almayacağız ve bunun yerine her şeyin işe yaramasına odaklanacağız. Sonucu GitHub'dan alabilirsiniz. Bu makaleyle birlikte takip etmek isterseniz projenize bırakabileceğiniz bir stil sayfası da var. Gezinme dahil aynı çerçeveyi elde etmek için pages/_app.js bu dosyayla değiştirin.

Atlamadan sonra daha fazlası! Aşağıdan okumaya devam edin ↓

Kurmak

create-next-app kullanarak yeni bir proje oluşturarak ve dizinine geçerek başlıyoruz:

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

Markdown dosyalarını daha sonra okumamız gerekecek. Bunu kolaylaştırmak için, başlamadan önce birkaç bağımlılık daha ekliyoruz.

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

Kurulum tamamlandıktan sonra, projemizi başlatmak için dev betiğini çalıştırabiliriz:

 multiauthor-blog$ yarn dev

Artık sitemizi keşfedebiliriz. Tarayıcınızda https://localhost:3000'i açın. Create-next-app tarafından eklenen varsayılan sayfayı görmelisiniz.

Oluştur-sonraki-app tarafından oluşturulan varsayılan sayfa.
Bunu görürseniz, kurulumunuz çalışır. (Büyük önizleme)

Birazdan sayfalarımıza ulaşmak için bir navigasyona ihtiyacımız olacak. Bunları pages/_app.js daha sayfalar var olmadan ekleyebiliriz.

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

Bu makale boyunca, gezinmenin işaret ettiği bu eksik sayfaları ekleyeceğiz. İlk önce bazı gönderiler ekleyelim, böylece bir bloga genel bakış sayfasında çalışacak bir şeyimiz olur.

Gönderi Oluşturma

İçeriğimizi koddan ayrı tutmak için gönderilerimizi _posts/ adlı bir dizine koyacağız. Yazmayı ve düzenlemeyi kolaylaştırmak için her gönderiyi bir Markdown dosyası olarak oluşturacağız. Her gönderinin dosya adı, daha sonra rotalarımızda bilgi olarak görev yapacak. Örneğin, _posts/hello-world.md dosyasına /posts/hello-world altında erişilebilir.

Tam başlık ve kısa bir alıntı gibi bazı bilgiler dosyanın başında ön planda yer alır.

 --- 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, …

Blogun boş başlamaması için bunun gibi birkaç dosya daha ekleyin:

 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/ └─ …

Kendinizinkini ekleyebilir veya bu örnek gönderileri GitHub deposundan alabilirsiniz.

Tüm Gönderileri Listeleme

Artık birkaç gönderimiz olduğuna göre, onları blogumuza almanın bir yoluna ihtiyacımız var. Hepsini listeleyen ve blogumuzun dizini olarak hizmet veren bir sayfa ekleyerek başlayalım.

Next.js'de pages/posts/index.js altında oluşturulan bir dosyaya sitemizde /posts olarak erişilebilir. Dosya, o sayfanın gövdesi olarak hizmet edecek bir işlevi dışa aktarmalıdır. İlk versiyonu şuna benziyor:

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

Çok uzağa gidemiyoruz çünkü henüz Markdown dosyalarını okumanın bir yolu yok. Zaten https://localhost:3000/posts adresine gidebiliriz, ancak yalnızca başlığı görüyoruz.

'Gönderiler' yazan bir başlığı olan boş bir sayfa.
Sayfamıza erişebilir ve onu hayatla doldurmaya başlayabiliriz. (Büyük önizleme)

Artık gönderilerimizi oraya ulaştırmanın bir yoluna ihtiyacımız var. Next.js, verileri bir sayfa bileşenine iletmek için getStaticProps() adlı bir işlev kullanır. İşlev, döndürülen nesnedeki props'ları props olarak bileşene iletir.

getStaticProps() , gönderileri, posts adında bir prop olarak bileşene ileteceğiz. Bu ilk adımda iki yer tutucu gönderiyi sabit kodlayacağız. Bu şekilde başlayarak, gerçek gönderileri daha sonra hangi formatta almak istediğimizi tanımlarız. Bir yardımcı fonksiyon onları bu formatta döndürürse, bileşeni değiştirmeden ona geçiş yapabiliriz.

Gönderiye genel bakış, gönderilerin tam metnini göstermez. Bu sayfa için her gönderinin başlığı, alıntısı, kalıcı bağlantısı ve tarihi yeterlidir.

 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", + } + ] + } + } +}

Bağlantıyı kontrol etmek için aksesuarlardan gönderileri alabilir ve bunları Posts bileşeninde gösterebiliriz. Başlığı, oluşturulma tarihini, alıntıyı ve gönderiye bir bağlantı ekleyeceğiz. Şimdilik, bu bağlantı henüz hiçbir yere götürmez.

 +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() { … }

Sayfayı tarayıcıda yeniden yükledikten sonra, şimdi şu iki gönderiyi gösteriyor:

İki yer tutucu gönderimizin listesi.
Bağlantı çalışıyor. Şimdi buraya gerçek gönderiler koymaya çalışabiliriz. (Büyük önizleme)

Tüm blog gönderilerimizi sonsuza kadar getStaticProps() içinde kodlamak istemiyoruz. Sonuçta, bu yüzden tüm bu dosyaları daha önce _posts/ dizininde oluşturduk. Şimdi bu dosyaları okumanın ve içeriklerini sayfa bileşenine aktarmanın bir yoluna ihtiyacımız var.

Bunu yapabilmemizin birkaç yolu var. Dosyaları doğrudan getStaticProps() içinde okuyabilirdik. Bu işlev istemcide değil sunucuda çalıştığından, içindeki fs gibi yerel Node.js modüllerine erişimimiz vardır. Sayfa bileşenini tuttuğumuz aynı dosyadaki yerel dosyaları okuyabilir, dönüştürebilir ve hatta değiştirebiliriz.

Dosyayı kısa tutmak ve tek bir göreve odaklanmak için bu işlevi bunun yerine ayrı bir dosyaya taşıyacağız. Bu şekilde, Posts bileşeninin yalnızca verileri görüntülemesi gerekir, ayrıca bu verilerin kendisini okumak zorunda kalmaz. Bu, projemize biraz ayrılık ve organizasyon katıyor.

Kural olarak, verileri okuyan işlevleri lib/api.js adlı bir dosyaya koyacağız. Bu dosya, onu görüntüleyen bileşenler için içeriğimizi alan tüm işlevleri tutacaktır.

Gönderilere genel bakış sayfası için tüm gönderileri okuyan, işleyen ve döndüren bir işlev istiyoruz. Biz buna getAllPosts() . İçinde, _posts/ dizinine giden yolu oluşturmak için önce path.join() kullanırız. Ardından, içindeki tüm dosyaların adlarını veren bu dizini okumak için fs.readdirSync() 'i kullanırız. Bu isimler üzerinde eşleme yaptıktan sonra sırayla her dosyayı okuyoruz.

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

Dosyayı okuduktan sonra içeriğini uzun bir string olarak alıyoruz. Ön maddeyi yazının metninden ayırmak için, bu dizgiyi gray-matter üzerinden çalıştırırız. Ayrıca, dosya adının sonundan .md kaldırarak her gönderinin bilgisini yakalayacağız. Gönderinin daha sonra erişilebilir olacağı URL'yi oluşturmak için bu bilgiye ihtiyacımız var. Bu işlev için gönderilerin Markdown gövdesine ihtiyacımız olmadığından, kalan içeriği görmezden gelebiliriz.

 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}`, + } }) }

...data burada döndürülen nesneye nasıl yaydığımıza dikkat edin. Bu, daha sonra {post.title} yerine {post.data.title} post.title} olarak kendi ön maddesinden değerlere erişmemizi sağlar.

Gönderilere genel bakış sayfamıza döndüğümüzde, artık yer tutucu gönderileri bu yeni işlevle değiştirebiliriz.

 +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(), } } }

Tarayıcıyı yeniden yükledikten sonra, daha önce sahip olduğumuz yer tutucular yerine artık gerçek gönderilerimizi görüyoruz.

Gerçek blog yazılarımızın bir listesi.
Yardımcı işlevi sayesinde bu sayfa artık gerçek gönderilerimizi gösteriyor. (Büyük önizleme)

Bireysel Gönderi Sayfaları Ekleme

Her gönderiye eklediğimiz bağlantılar henüz hiçbir yere götürmüyor. Henüz /posts/hello-world gibi URL'lere yanıt veren bir sayfa yok. Dinamik yönlendirme ile bunun gibi tüm yollarla eşleşen bir sayfa ekleyebiliriz.

pages/posts/[slug].js olarak oluşturulan bir dosya, /posts/abc gibi görünen tüm URL'lerle eşleşir. URL'de [slug] yerine görünen değer, sayfada bir sorgu parametresi olarak kullanılabilir. Bunu ilgili sayfanın getStaticProps() dosyasında bir yardımcı işlevi çağırmak için params.slug olarak kullanabiliriz.

getAllPosts() işlevinin karşılığı olarak, bu yardımcı işlevi getPostBySlug(slug) . Tüm gönderiler yerine, geçtiğimiz sümüklü böcekle eşleşen tek bir gönderi döndürür. Bir gönderinin sayfasında, temel alınan dosyanın Markdown içeriğini de göstermemiz gerekir.

Tek tek gönderiler için sayfa, gönderiye genel bakış sayfası gibi görünür. getStaticProps() içindeki sayfaya posts iletmek yerine, yalnızca tek bir post iletiyoruz. Gönderinin Markdown gövdesini kullanılabilir HTML'ye nasıl dönüştüreceğimize bakmadan önce genel kurulumu yapalım. Bir sonraki adımda hemen ekleyeceğimiz yardımcı işlevi kullanarak burada yer tutucu gönderisini atlayacağız.

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

Şimdi yardımcı dosyamız lib/api.js getPostBySlug(slug) işlevini eklememiz gerekiyor. Birkaç dikkate değer farkla, getAllPosts() gibidir. Gönderinin dosya adını slug'dan alabileceğimiz için, önce dizinin tamamını okumamız gerekmez. Bilgi 'hello-world' ise, _posts/hello-world.md adlı bir dosya okuyacağız. Bu dosya yoksa, Next.js bir 404 hata sayfası gösterecektir.

getAllPosts() 'un diğer bir farkı da bu sefer yazının Markdown içeriğini de okumamız gerektiğidir. Onu, önce remark ile işleyerek ham Markdown yerine render-hazır HTML olarak döndürebiliriz.

 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, + } +}

Teoride, getAllPosts() getPostBySlug(slug) içinde kullanabiliriz. İlk önce onunla tüm gönderileri alırdık, ardından verilen sümüklü böcekle eşleşen birini arayabiliriz. Bu, gereksiz bir iş olan tek bir tane almadan önce her zaman tüm gönderileri okumamız gerektiği anlamına gelir. getAllPosts() ayrıca gönderilerin Markdown içeriğini döndürmez. Bunu yapmak için güncelleyebiliriz, bu durumda şu anda ihtiyaç duyduğundan daha fazla iş yapar.

İki yardımcı işlev farklı şeyler yaptığı için onları ayrı tutacağız. Bu şekilde, işlevleri tam olarak ve yalnızca her birinin yapması gereken işe odaklayabiliriz.

Dinamik yönlendirme kullanan sayfalar, getStaticPaths() getStaticProps() . Bu işlev, Next.js'ye dinamik yol segmentlerinin hangi değerleri için sayfalar oluşturacağını söyler. Bunları getAllPosts() kullanarak ve her gönderinin slug öğesini tanımlayan nesnelerin bir listesini döndürerek sağlayabiliriz.

 -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, + }, + })), + } +}

Markdown içeriğini getPostBySlug(slug) içinde ayrıştırdığımız için artık sayfada render edebiliyoruz. Next.js'nin HTML'yi post.body arkasında oluşturabilmesi için bu adım için dangerouslySetInnerHTML kullanmamız gerekiyor. Adına rağmen, bu senaryoda özelliği kullanmak güvenlidir. Gönderilerimiz üzerinde tam kontrole sahip olduğumuz için, güvenli olmayan komut dosyaları eklemeleri pek olası değildir.

 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() { … }

Gönderiye genel bakıştaki bağlantılardan birini takip edersek, şimdi o gönderinin kendi sayfasına gideriz.

Tek bir gönderinin sayfası.
Gönderinin içeriğini gösterebiliriz, ancak henüz kimin yazdığını bilmiyoruz. (Büyük önizleme)

Yazar Ekleme

Artık gönderilerimizi bağladığımıza göre, yazarlarımız için aynı adımları tekrarlamamız gerekiyor. Bu sefer, onları tanımlamak için Markdown yerine JSON kullanacağız. Aynı projede farklı dosya türlerini ne zaman mantıklı olursa bu şekilde karıştırabiliriz. Dosyaları okumak için kullandığımız yardımcı işlevler, bizim için herhangi bir farklılıkla ilgilenir. Sayfalar, içeriğimizi hangi biçimde depoladığımızı bilmeden bu işlevleri kullanabilir.

İlk olarak, _authors/ adında bir dizin oluşturun ve buna birkaç yazar dosyası ekleyin. Gönderilerde yaptığımız gibi, dosyaları her yazarın bilgisine göre adlandırın. Bunu daha sonra yazarları aramak için kullanacağız. Her dosyada, bir JSON nesnesinde bir yazarın tam adını belirtiriz.

 { "name": "Adrian Webber" }

Şimdilik projemizde iki yazar olması yeterli.

Onlara biraz daha kişilik kazandırmak için, her yazar için bir profil resmi de ekleyelim. Bu statik dosyaları public/ dizinine koyacağız. Dosyaları aynı sümüklü böcekle adlandırarak, yalnızca ima edilen kuralı kullanarak onları birbirine bağlayabiliriz. İkisini birbirine bağlamak için resmin yolunu her yazarın JSON dosyasına ekleyebiliriz. Tüm dosyaları sümüklü böceklere göre adlandırarak, bu bağlantıyı yazmak zorunda kalmadan yönetebiliriz. JSON nesnelerinin yalnızca kodla oluşturamayacağımız bilgileri tutması gerekir.

İşiniz bittiğinde, proje dizininiz şöyle görünmelidir.

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

Gönderilerde olduğu gibi, şimdi tüm yazarları okumak ve bireysel yazarları almak için yardımcı işlevlere ihtiyacımız var. Yeni getAllAuthors() ve getAuthorBySlug(slug) işlevleri de lib/api.js içinde yer alır. Postadaki meslektaşlarıyla neredeyse aynı şeyi yapıyorlar. Yazarları tanımlamak için JSON kullandığımız için, burada herhangi bir Markdown'ı remark ile ayrıştırmamız gerekmez. Ayrıca ön maddeyi ayrıştırmak için gray-matter ihtiyacımız yok. Bunun yerine, dosyalarımızın metin içeriğini nesnelere okumak için JavaScript'in yerleşik JSON.parse() işlevini kullanabiliriz.

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

Bu bilgiyle, yardımcı fonksiyonlarımız şöyle görünür:

 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, + } +}

Yazarları uygulamamıza okumanın bir yolu ile artık hepsini listeleyen bir sayfa ekleyebiliriz. pages/authors/index.js altında yeni bir sayfa oluşturmak bize sitemizde bir /authors sayfası verir.

Yardımcı işlevler, dosyaları bizim için okumakla ilgilenir. Bu sayfa bileşeninin, yazarların dosya sistemindeki JSON dosyaları olduğunu bilmesi gerekmez. Verilerini nereden ve nasıl aldığını bilmeden getAllAuthors() kullanabilir. Yardımcı fonksiyonlarımız verilerini birlikte çalışabileceğimiz bir biçimde döndürdüğü sürece biçimin önemi yoktur. Bunun gibi soyutlamalar, uygulamamız genelinde farklı içerik türlerini karıştırmamıza olanak tanır.

Yazarlar için dizin sayfası, gönderiler için olana çok benzer. Tüm yazarları, onları Authors bileşenine getStaticProps() içinde alırız. Bu bileşen, her yazarla eşleşir ve onlar hakkında bazı bilgileri listeler. Slug'dan başka bağlantılar veya URL'ler oluşturmamıza gerek yok. Yardımcı işlev, yazarları zaten kullanılabilir bir biçimde döndürür.

 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(), }, } }

Sitemizde /authors ziyaret edersek, tüm yazarların adları ve resimleriyle birlikte bir listesini görürüz.

Yazarların listesi.
Tüm yazarları listeleyebiliriz, ancak kaç makaleye katkıda bulunduklarını bilmenin bir yolu yoktur. (Büyük önizleme)

Yazarların profillerine bağlantılar henüz bir yere götürmez. Profil sayfalarını eklemek için pages/authors/[slug].js altında bir dosya oluşturuyoruz. Yazarların herhangi bir metin içeriği olmadığı için şimdilik sadece isimleri ve profil resimleri ekleyebiliyoruz. Ayrıca, Next.js'ye hangi sümüklü böceklerin sayfalar oluşturacağını söylemek için başka bir getStaticPaths() 'e ihtiyacımız var.

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

Bununla, artık bilgiye çok açık olan temel bir yazar profili sayfamız var.

Bir yazarın adını ve vesikalık fotoğrafını gösteren profil sayfası.
Bir yazarın profil sayfası şu anda çoğunlukla boş. (Büyük önizleme)

Bu noktada, yazarlar ve gönderiler henüz bağlantılı değil. Sırada bu köprüyü kuracağız, böylece her yazarın gönderilerinin bir listesini profil sayfalarına ekleyebiliriz.

Gönderileri ve Yazarları Bağlama

İki içerik parçasını birbirine bağlamak için, birini diğerine göndermemiz gerekir. Gönderileri ve yazarları sümüklü böcekleriyle zaten tanımladığımız için, onlara bununla atıfta bulunacağız. Gönderilere yazarlar ve yazarlara gönderiler ekleyebiliriz, ancak onları bağlamak için bir yön yeterlidir. Gönderileri yazarlara atfetmek istediğimizden, her gönderinin ön maddesine yazarın bilgisini ekleyeceğiz.

 --- 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, …

Bunu devam ettirirsek, gray-matter üzerinden çalıştırmak, yazar alanını gönderiye bir dize olarak ekler:

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

Yazarı temsil eden nesneyi almak için, o bilgi parçasını kullanabilir ve getAuthorBySlug(slug) çağırabiliriz.

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

Yazarı tek bir gönderinin sayfasına eklemek için, getStaticProps() içinde getStaticProps() getAuthorBySlug(slug) öğesini bir kez çağırmamız gerekir.

 +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), + }, }, } }

...post getStaticProps() içinde post olarak da adlandırılan bir nesneye nasıl yaydığımıza dikkat edin. author bu satırın arkasına yerleştirerek, yazarın dize sürümünü tam nesnesiyle değiştiririz. Bu, Post bileşenindeki post.author.name aracılığıyla bir yazarın özelliklerine erişmemizi sağlar.

Bu değişiklikle birlikte, artık bir gönderinin sayfasında, adı ve resmiyle birlikte yazarın profil sayfasına bir bağlantı alıyoruz.

Artık yazarın adını ve vesikalık fotoğrafını içeren bitmiş gönderi sayfası.
Gönderi şimdi düzgün bir şekilde yazara atfedildi. (Büyük önizleme)

Yazıya genel bakış sayfasına yazar eklemek de benzer bir değişiklik gerektiriyor. getAuthorBySlug(slug) bir kez çağırmak yerine, tüm gönderileri eşleştirmemiz ve her biri için onu çağırmamız gerekir.

 +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), + })), } } }

Bu, gönderiye genel bakıştaki her gönderiye yazarları ekler:

Yazarlarının adları ve vesikalık fotoğrafları da dahil olmak üzere blog gönderilerinin bir listesi.
Bu şimdi gerçek bir blog gibi görünüyor. (Büyük önizleme)

JSON dosyasına bir yazarın gönderilerinin bir listesini eklememiz gerekmez. Profil sayfalarında önce getAllPosts() ile tüm gönderileri alıyoruz. Daha sonra bu yazara atfedilenler için tam listeyi filtreleyebiliriz.

 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() { … }

Bu bize her yazarın profil sayfasındaki makalelerin bir listesini verir.

Bir yazarın profil sayfası, şimdi gönderilerine bağlantıların bir listesini içerir.
Artık her yazarın gönderilerini listeleyebilir ve bunlara bağlantı verebiliriz. (Büyük önizleme)

Yazara genel bakış sayfasında, arayüzü karmaşıklaştırmamak için yalnızca kaç gönderi yazdıklarını ekleyeceğiz.

 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), + })), } } }

Bununla birlikte, Yazarlara genel bakış sayfası, her yazarın kaç gönderiye katkıda bulunduğunu gösterir.

Gönderi sayılarıyla birlikte yazarların listesi.
Artık her yazarın girdisine katkıda bulunan gönderilerin sayısını koyabiliriz. (Büyük önizleme)

Ve bu kadar! Gönderiler ve yazarlar artık tamamen bağlantılı. Bir gönderiden bir yazarın profil sayfasına ve oradan diğer gönderilerine gidebiliriz.

Özet ve Görünüm

Bu makalede, birbiriyle ilişkili iki içerik türünü benzersiz sümüklü böcekler aracılığıyla birbirine bağladık. Gönderiden yazara ilişkinin tanımlanması, çeşitli senaryoları mümkün kıldı. Artık yazarı her gönderide gösterebilir ve gönderilerini profil sayfalarında listeleyebiliriz.

Bu teknikle, başka birçok ilişki türü ekleyebiliriz. Her gönderinin bir yazarın üstünde bir gözden geçireni olabilir. Bunu, bir gönderinin ön maddesine bir reviewer alanı ekleyerek ayarlayabiliriz.

 --- 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, …

Dosya sisteminde gözden geçiren, _authors/ dizininden başka bir yazardır. Onların bilgilerini almak için de getAuthorBySlug(slug) kullanabiliriz.

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

Hatta bir gönderide tek bir kişi yerine iki veya daha fazla yazar adını vererek ortak yazarları destekleyebiliriz.

 --- 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, …

Bu senaryoda, bir gönderinin getStaticProps() artık tek bir yazar arayamayız. Bunun yerine, hepsini elde etmek için bu yazar dizisini eşlerdik.

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

Bu teknikle başka tür senaryolar da üretebiliriz. Her türlü bire bir, bire çoğa ve hatta çoktan çoğa ilişkiye olanak tanır. Projenizde haber bültenleri ve vaka çalışmaları da varsa, bunların her birine yazarlar da ekleyebilirsiniz.

Marvel evreni ile ilgili bir sitede, karakterleri ve göründükleri filmleri birbirine bağlayabiliriz. Sporda, oyuncuları ve şu anda oynadıkları takımları birbirine bağlayabiliriz.

Yardımcı işlevler veri kaynağını gizlediğinden, içerik farklı sistemlerden gelebilir. Dosya sisteminden makaleleri, bir API'den gelen yorumları okuyabilir ve bunları kodumuzda birleştirebiliriz. İçeriğin bir parçası başka bir içerik türüyle ilgiliyse, onları bu kalıpla ilişkilendirebiliriz.

Diğer Kaynaklar

Next.js, Veri Alma ile ilgili sayfalarında kullandığımız işlevler hakkında daha fazla arka plan sunar. Farklı kaynak türlerinden veri getiren örnek projelere bağlantılar içerir.

Bu başlangıç ​​projesini daha ileriye taşımak istiyorsanız şu makalelere göz atın:

  • Strapi ve Next.js ile CSS Hileleri Web Sitesi Klonu Oluşturma
    Yerel dosya sistemindeki dosyaları, Strapi destekli bir arka uçla değiştirin.
  • Next.js'de Şekillendirme Yöntemlerini Karşılaştırma
    Bu başlatıcının stilini değiştirmek için özel CSS yazmanın farklı yollarını keşfedin.
  • Next.js ile Markdown/MDX
    Markdown'ınızda JSX ve React bileşenlerini kullanabilmeniz için projenize MDX ekleyin.