Next.js ile Çok Yazarlı Bir Blog Oluşturma
Yayınlanan: 2022-03-10Bu 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:
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.
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.
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.
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:
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.
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.
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 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.
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.
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:
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.
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.
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.