Merhaba Dünya! GatsbyJS'e Başlangıç Kılavuzu

React, NodeJS ve GraphQL'le Statik HTML Siteler Oluşturma

27.08.2018 Pazartesi
React
19 dakika

Yazdıklarımın okunmayarak boşa gideceği gibi yersiz bir fikre kapıldığımdan, bugüne dek bir blog dahi açmadım. Sonra bir gün, her nereden estiyse, Etiya içinde yayınlamak üzere hazırladığım Frontend Teknolojileri Konferansı 2018 notlarını Twitter’dan paylaştım. Bu paylaşım, yeniden paylaşan ve beğenenlerin de katkısıyla 10 binin üzerinde insana ulaştı, yüzlerce kez de okundu. 😳 Yanılmıştım!

O gün karar verdim: Bir blog açıp, -artık ne kadar olursa- elimden geldiğince Türkçe içerik üretecektim. Günlük işlerimden vakit bulabildiğim kadarıyla blogumu hazırlamaya başladım. Altyapı olarak bir süredir severek kullandığım GatsbyJS‘te karar kıldım ve vakit kaybetmemek açısından bir başlangıç kitinden hareketle ziyaret etmiş olduğunuz siteyi kurdum. Okuduğunuz bu ilk makalede de Gatsby ile basit bir blogun nasıl kurulabileceğini adım adım anlatmaya çalışacağım.

Uyarı: Doğası gereği biraz uzun bir yazı oldu. Özür dilerim. 😞

Daha fazlasını yap...
Fotoğraf: Carl Heyerdahl, Unsplash

Blogum için GatsbyJS’i Neden Seçtim?

Bu sorunun benim açımdan cevabı net: JavaScript.

React + NodeJS + GraphQL = 💛

GatsbyJS.org’da sıralanan avantajlarsa şöyle:

  • Modern web teknolojilerine dayanıyor. Anlayacağınız işin içinde Webpack var.
  • GraphQL ve plugin sisteminden yararlanarak verilerinizi çok çeşitli kaynaktan (DB, CMS, SaaS, Markdown, vs.) içeri alarak başlayabilirsiniz.
  • JAMstack ile ilgili ağdalı bir şeyler… 😩
  • Çıktısı kademeli ağ uygulaması (progressive web app, PWA) oluyor.
  • Rakiplerine kıyasla hızlı yüklenen siteler üretiyor.
  • Ölçeklendirmesi zahmetsiz. Sonuçta statik sitelerden bahsediyoruz.

GatsbyJS Nasıl Çalışıyor?

Aşağıdaki kaynakların herhangi birinden GraphQL sorgularıyla veri çekiyor:

  • SQL veya NoSQL veritabanları
  • Markdown, JSON, YAML, CSV, vb. dosyalar
  • Wordpress, Drupal, Contentful, CosmicJS, vb. CMS’ler
  • Servisler

Sonra da bunları sunucu taraflı (server-side) React ve NodeJS’in yardımıyla statik HTML, CSS ve istemci taraflı (client-side) React’e derliyor. Ortaya çıkan sonuç, dizin ve dosya yapısı tüm sunuculara uygun olduğundan, başka adım gerektirmeksizin hemen yayına alınabiliyor.

GatsbyJS ile Statik Site Oluşturmaya Nasıl Başlarsınız?

NodeJS yoksa önce onu kurarsınız. Varsa terminalinizde şunu yazıp çalıştırmanız yeterli:

npx gatsby-cli new benim-gatsby-projem https://github.com/gatsbyjs/gatsby-starter-default#v2

📘 npx yürütülebilir NodeJS paketlerini sisteminize kurmanıza gerek duymaksızın çalıştırabilen bir npm CLI’ı ve 5.2.0 versiyonundan beri npm’e dahil olduğundan, Node’un çok eski sürümlerinden biriyle çalışmıyorsanız, sisteminizde büyük olasılıkla zaten mevcut. Değilse de yükleyin; nitekim deneme projesi için sisteminize global bir paket yükleyip ortamınızı kirletmekten %83.75 daha iyidir. Kesin bilgi…

CLI, belirttiğimiz şablonu (gatsby-starter-default#v2) sisteminize kopyaladıktan sonra içindeki package.json‘a göre bağımlılıkları (dependency) kuruyor. Sonunda sanki sihir yapmış gibi terminale mesaj basıyor. Bkz. ✨

Gatsby CLI ile yeni Gatsby projesi oluşturma

Kısa bir kurulumdan sonra, cd benim-gatsby-projem yazarak yeni dizine girip npm start komutunu çalıştırıyoruz…

Ve hata alıyoruz, çünkü projede böyle bir komut dizisi (script) tanımlı değil. 🤣

Gatsby, ezbere kullandığımız start komut dizisini boşta bırakmış, geliştirme sunucusunu develop ile başlatıyor. O yüzden npm run develop yazıp serçe parmağımızla Enter tuşuna basıyoruz. CLI’ın temizlik ve derleme işlemlerini bitirip webpack-dev-serverı ayağa kaldırmasını bekledikten sonra, tarayıcımızla http://localhost:8000 adresine giriyoruz.

Tadaaaaa!!! 🎉

Gatsby CLI içerisinde routing hazır bir proje yaratıyor

Projeyi açıp göz gezdirin. Ana dizindeki gatsby- ile başlayan JavaScript dosyalarıyla iki dizin hemen dikkatinizi çekecektir: pages ve components. Bunları inceleyin; ama fazla vakit harcamayın; çünkü henüz pek bir numaraları yok.

📘 React’e bağımlı olduğu için, Gatsby IE9+ ve diğer popüler tarayıcılarda çalışıyor. IE8’i desteklemek gibi bir derdiniz varsa, üzgünüm… 🙄

GatsbyJS’te Tüketmek Üzere İçerik Üretme

Eğer pages dizinini açıp incelediyseniz, Gatsby’nin iki yol (route) arasında geçişi nasıl yönettiğini anlamışsınızdır:

<Link to="/page-2/">Go to page 2</Link>

Kısacası ziyaretçiyi sitenizde gezdirmek Gatsby’nin <Link> bileşeninden geçiyor. Lakin bu kullanım statik. Bizim yazacağımız makalelerinse adının veya yolunun ne olacağı bile belli değil. Yazılarımızı listelemenin dinamik bir yöntemini bulmalıyız.

O zaman önce saçmasapan bir makale yaratalım. Hemen src‘nin altına posts adlı bir dizin açıp, onun da altına merhaba-dunya dizinini oluşturuyor, içine de şöyle bir index.md dosyası ekliyoruz:

---

title: "Merhaba Dünya"
date: "2018-08-14T19:29:22.587Z"

---

Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque quis optio corrupti quae commodi ipsam nesciunt natus aliquam dignissimos suscipit nostrum libero voluptatibus ab iure quibusdam, nemo quia assumenda modi!

Rerum corrupti ratione molestiae porro qui laboriosam quo voluptates quisquam quaerat recusandae suscipit quidem, quae nobis! Quis illo excepturi repellat laudantium distinctio, vel recusandae, eius iusto molestias suscipit nihil. Dolorum.

Suscipit itaque quod inventore magnam harum sunt illum quaerat nihil consectetur sed veniam dignissimos, est blanditiis alias, doloremque corrupti. Eos laboriosam voluptas officiis quos non aut excepturi saepe amet asperiores.

Soluta tenetur quidem unde nisi nostrum inventore. Corrupti expedita omnis maiores illo, doloribus culpa reprehenderit fugiat illum error recusandae animi ut consequuntur vero, quia provident laudantium eum, ratione quae aspernatur.

Markdown ile hazırladığımız /src/posts/merhaba-dunya/index.md yolundaki ilk makalemiz hazır. Mıgırca yazılardan oluşuyor, ama işimizi görür. Şimdi bunu tüketilebilir hale getirelim.

GatsbyJS ile Dinamik İçerik Nasıl Çekilir?

Gatsby’de hemen her şey için bir eklenti (plugin) bulmak mümkün. Dosyaları veri kaynağına çevirmekte “gatsby-source-filesystem” kaynak eklentisini (source plugin), Markdown’ı HTML ve ön bilgiye (frontmatter) çevirmekteyse “gatsby-transformer-remark” dönüştürücü eklentisini (transformer plugin) kullanabiliyoruz. Şunları projemize bir kuralım:

npm install gatsby-source-filesystem gatsby-transformer-remark

Sonra da gatsby.config.js dosyasını açıp plugins kısmının sonuna yeni eklentilerimizi yerleştirelim.

.
.
.
  plugins: [
    .
    .
    .
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `posts`,
        path: `${__dirname}/src/posts/`,
      },
    },
    'gatsby-transformer-remark'
  ],
.
.
.

☠️ Noktalarla gösterilen yerlerde projenizin çalışması için gerekli başka yapılandırmalar var. Silmeyin lütfen!

Şimdi, terminalden geliştirme sunucusunu Ctrl + C ile kapatıp npm run develop ile yeniden çalıştırdıktan sonra GraphiQL arayüzüne şu adresten ulaşabiliriz: http://localhost:8000/___graphql

Bu arayüzü, hatta GraphQL’i daha önce görmemiş olabilirsiniz. Hiç sorun değil. Kullanacağımız sorgu şöyle bir şey:

query NewestPosts {
  posts: allMarkdownRemark(
    filter: {frontmatter: { title: { ne: "" }, date: { ne: "" } }},
    sort: {fields: [frontmatter___date], order: DESC},
    limit: 5,
    skip: 0
  ) {
    edges {
      node {
        id
        frontmatter {
          title
          date(formatString: "DD MMMM YYYY, HH:mm", locale: "tr")
        }
        excerpt
      }
    }
    totalCount
  }
}

Fazla detaya inmeden sorgumuzun ne yaptığını açıklayayım:

  • En baştaki query NewestPosts sorguya adıyla başvurabilmek amaçlı ve zorunlu değil.
  • Kurduğumuz iki eklenti, allMarkdownRemark adında sorgulanabilir bir bağlantı (connection) sunuyor.
  • Bağlantıyı posts olarak rumuzluyoruz (alias), çünkü paşa gönlüm öyle istedi.
  • Çekmek istediğimiz sonuçları şunlarla belirliyoruz:

    • filter ile hangi sonuçların elenmeyeceğini (başlık ve tarih ön bilgisi boş olmayanlar)
    • sort ile sonuçların nasıl sıralanacağını (tarihe göre yeniden eskiye)
    • limit ile en fazla kaç sonuç döneceğini (5)
    • skip ile kaç kayıt atlanarak başlanacağını (0)
  • edges meselesine bu yazıda girmeyeceğim. 🤐 Daha fazlası için şimdilik bkz. Explaining GraphQL Connections
  • node GatsbyJS’in veri modelini üzerine kurguladığı özel bir nesne (object).
  • id her node‘da bulunan bir alan (field). Makaleleri React’le listelerken key olarak kullanacağız.
  • frontmatter ve excerpt çevirici eklentimizin bize güzellikleri. Birincisi makalenin en üstüne yerleştirdiğimiz ön bilgileri getirirken, ikincisi ana metnin ilk 137 karakterlik kısmını sunuyor.
  • date alanındaki formatString ise ön bilgiden gelen tarihi biçimlendirebilmenizi sağlıyor.
  • Son olarak filtremizden geçen toplam sonuç sayısını totalCount ile çekebiliyoruz.

Yukarıdaki kodu kopyalayıp GraphiQL’in sol penceresine yapıştırın ve en üstte duran tipsiz oynat tuşuna basın. ▶️ Sağ tarafta aşağıdaki JSON sonuç çıkacak:

{
  "data": {
    "posts": {
      "edges": [
        {
          "node": {
            "id": "/Users/LeventArmanOzak/armanozak/benim-gatsby-projem/src/posts/merhaba-dunya/index.md absPath of file >>> MarkdownRemark",
            "frontmatter": {
              "title": "Merhaba Dünya",
              "date": "14 Ağustos 2018, 19:29"
            },
            "excerpt": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque quis optio corrupti quae commodi ipsam nesciunt natus aliquam dignissimos..."
          }
        }
      ],
      "totalCount": 1
    }
  }
}

GraphQL ile veri sorgulamanın ne kadar basit olduğunu görüyorsunuz. Şimdi bu veriyi sitemizde nasıl gösterebileceğimize bir bakalım.

Gatsby’de Dinamik İçeriği Nasıl Tüketirsiniz?

Yine pages dizininin altındaki index.js dosyasını açın, en alta yukarıdaki sorguyu ekleyerek başlayın ve bileşenin içeriğini şöyle güncelleyin:

import React from 'react'
import { graphql } from 'gatsby'

import Layout from '../components/layout'

const IndexPage = ({
  data: {
    posts: {
      edges,
      totalCount
    }
  },
}) => (
  <Layout>
    <small>
      Toplam {totalCount} makale
    </small>

    <ul>
      {edges.map(({ node }) => (
        <li key={node.id}>
          <h2>{node.frontmatter.title}</h2>
          <small>{node.frontmatter.date}</small>
          <p>{node.excerpt}</p>
        </li>
      ))}
    </ul>
  </Layout>
)

export default IndexPage

export const pageQuery = graphql`
  query NewestPosts {
    posts: allMarkdownRemark(
      filter: { frontmatter: { title: { ne: "" }, date: { ne: "" } } }
      sort: { fields: [frontmatter___date], order: DESC }
      limit: 5
      skip: 0
    ) {
      edges {
        node {
          id
          frontmatter {
            title
            date(formatString: "DD MMMM YYYY, HH:mm", locale: "tr")
          }
          excerpt
        }
      }
      totalCount
    }
  }
`

Neler yaptık?

  • pageQuery adında bir sabit oluşturup daha önce hazırladığımız sorguyu graphql etiketli şablon kalıbını (tagged template literal) kullanarak çalışır hale getirdik ve sabitimizi dışa verdik (export) ki Gatsby bunu okuyup ilgili veriyi sağlasın.
  • IndexPage fonksiyonel bileşeninin (functional component) argüman olarak aldığı özelliklerden (props) bize gerekenleri çıkardık (destructure).
  • Layout bileşeninin içerisini değiştirip kaç kayıt olduğunu ve çektiğimiz makale özetlerini listeledik.

🔎 İç içe çıkarma (nested destructuring) işlemi tanıdık gelmeyebilir. Yaptığımız aslında şundan pek de farklı değil:

const { edges, totalCount } = props.data.posts

Eğer geliştirme sunucusu hala ayaktaysa, tarayıcınızı açtığınızda hazırdaki tek makalenin başlığını, tarihini ve özet bilgisini görüyorsunuzdur. 🤮 Çirkin, biliyorum; ama bu yazının konusu kozmetik unsurlar değil.

GatsbyJS ile makaleleri listelemek çok kolay

Peki neleri yapmadık?

  • Dinamik içerik üretiyoruz, ancak ürettiğimiz içeriğe nasıl bağlantı vereceğimizi bilmiyoruz. Bunun için bir yöntem geliştirmeliyiz.
  • Yazımızı okuyabileceğimiz bir sayfa da üretmedik. Ne bir şablonumuz, ne de otomatik olarak görüntülenebilir bir .js dosyamız mevcut.
  • Sayfalama (pagination) yapmadık. Evet, toplam kayıt sayısını gösterebiliyoruz; ama makalelerimizin bir bölümünü listelemek üzere ayarlanmış sayfalarımız bulunmuyor. Ayrıca imleç (cursor) kaçıncı kayıtta, bunu da bilmiyoruz.

Belli ki daha çok işimiz var. 😒 Adım adım eksiklerimizi giderme zamanı.

Gatsby’de SEO Dostu Temiz Adresler Oluşturma

Yazımızın yolunu oluşturup çektiğimiz veriye dahil etmek amacıyla gatsby-node.js dosyasını açıp içine aşağıdaki kodu ekliyoruz:

const { createFilePath } = require('gatsby-source-filesystem')

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `MarkdownRemark`) {
    const value = createFilePath({ node, getNode, trailingSlash: false })
    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

Burada node daha önce GraphQL sorgumuzda da adı geçen Gatsby’ye özel nesne, getNode bunları yakalamaya yarayan metot, actions ise çağırıldıklarında Redux eylemi (action) oluşturup fırlatan (dispatch) fonksiyonlar kümesi. Bunlardan biri olan createNodeField ile eklentimizden içe aldığımız (import) createFilePath fonksiyonunu birlikte kullanarak slug adında özel bir alan yaratıyoruz ve onCreateNode adıyla dışa veriyoruz. Sebebi için bkz. GatsbyJS Node API

Artık GraphiQL arayüzünden şöyle bir sorgu yapabiliyoruz:

query NewestPosts {
  posts: allMarkdownRemark(
    filter: {frontmatter: { title: { ne: "" }, date: { ne: "" } }},
    sort: {fields: [frontmatter___date], order: DESC},
    limit: 5,
    skip: 0
  ) {
    edges {
      node {
        fields {
          slug
        }
        frontmatter {
          title
          date(formatString: "DD MMMM YYYY, HH:mm", locale: "tr")
        }
        excerpt
      }
    }
    totalCount
  }
}

Ve şu cevabı alıyoruz:

{
  "data": {
    "posts": {
      "edges": [
        {
          "node": {
            "fields": {
              "slug": "/merhaba-dunya"
            },
            "frontmatter": {
              "title": "Merhaba Dünya",
              "date": "14 Ağustos 2018, 19:29"
            },
            "excerpt": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque quis optio corrupti quae commodi ipsam nesciunt natus aliquam dignissimos…"
          }
        }
      ],
      "totalCount": 1
    }
  }
}

Çok güzel! Haydi makale sayfamızı üretelim.

GatsbyJS’te Şablon Sayfa Oluşturma

Şablon yaratmak çok basit. Bunun için src‘nin altına templates adında bir dizin açın ve içerisine PostTemplate.js adlı bir dosya ekleyin. Dosyanın içeriği şöyle olacak:

import React from 'react'
import { graphql, Link } from 'gatsby'

import Layout from '../components/layout'

const PostTemplate = ({
  data: {
    post: {
      frontmatter, 
      html, 
      timeToRead
    }
  }
}) => (
  <Layout>
    <h1>{frontmatter.title}</h1>
    <small>📅{frontmatter.date} &nbsp; 🕑{timeToRead} dakika</small>
    <div dangerouslySetInnerHTML={{ __html: html }} />
    <Link to="..">Anasayfaya dön</Link>
  </Layout>
)

export default PostTemplate

export const pageQuery = graphql`
  query PostBySlug($slug: String!) {
    post: markdownRemark(
      fields: { slug: { eq: $slug } }
    ) {
      html
      timeToRead
      frontmatter {
        title
        date(formatString: "DD MMMM YYYY, HH:mm", locale: "tr")
      }
    }
  }
`

Burada, yukarıda anlatılanlara göre iki fark var:

  • Niyetimiz tekil sonuç almak olduğu için, allMarkdownRemark bağlantısını değil, markdownRemark bağlantısını sorguluyoruz.
  • Bu sorgu, fieldsın altındaki slug alanıyla karşılaştırmak için String tipinde bir $slug parametresi alıyor.

📘 Sorgu parametreleri $ işaretiyle başlıyor ve sonunda ! olması parametrenin zorunlu olduğunu ifade ediyor.

Gatsby’de Şablonları Kullanarak Statik Sayfalar Derleme

gatsby-node.js dosyasını tekrar açıp, aşağıda işaretli kısımları ekleyin:

const path = require('path')
const { createFilePath } = require('gatsby-source-filesystem')

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `MarkdownRemark`) {
    const value = createFilePath({ node, getNode, trailingSlash: false })
    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions
  const postTemplate = path.resolve('./src/templates/PostTemplate.js')

  return new Promise((resolve, reject) => {
    resolve(
      graphql(`
        {
          posts: allMarkdownRemark(
            filter: { frontmatter: { title: { ne: "" }, date: { ne: "" } } }
            sort: { fields: [frontmatter___date], order: DESC }
            limit: 1000
          ) {
            edges {
              node {
                fields {
                  slug
                }
              }
            }
          }
        }
      `).then(({errors, data}) => {
        if (errors) {
          console.log(errors)
          reject(errors)
        }

        const { edges } = data.posts

        edges.forEach(({ node: { fields: context } }) =>
          createPage({
            context,
            path: context.slug,
            component: postTemplate,
          })
        )
      })
    )
  })
}

Bu noktada geliştirme sunucusunu yeniden başlatmanız gerekecektir. Kısaca açıklamak gerekirse, GraphQL ile son 1000 kaydı çekip, her bir kayıt için yeni oluşturduğumuz şablonu ve slug alanını createPage Redux eylem üreticisine (action creator) geçirerek ilgili makalenin sayfasını üretiyoruz.

CLI işini bitirdikten sonra http://localhost:8000/merhaba-dunya adresine girdiğinizde aşağıdaki tabloyla karşılaşacaksınız:

GatsbyJS ile hazırlanmış örnek makale sayfası

Bu sayfa işimizi görüyor. Şimdi ona nasıl ulaşacağımızı görelim.

GatsbyJS’te İçeriğe Bağlantı Verme

pages dizinindeki index.js dosyasını yeniden açıp, içerisinde aşağıda gösterilen değişiklikleri yapalım.

import React from 'react'
import { graphql, Link } from 'gatsby'

import Layout from '../components/layout'

const IndexPage = ({
  data: {
    posts: {
      edges,
      totalCount
    }
  },
}) => (
  <Layout>
    <small>
      Toplam {totalCount} makale
    </small>

    <ul>
      {edges.map(({ node }) => (
        <li key={node.fields.slug}>
          <Link to={node.fields.slug}>
            <h2>{node.frontmatter.title}</h2>
          </Link>
          <small>{node.frontmatter.date}</small>
          <p>{node.excerpt}</p>
        </li>
      ))}
    </ul>
  </Layout>
)

export default IndexPage

export const pageQuery = graphql`
  query NewestPosts {
    posts: allMarkdownRemark(
      filter: { frontmatter: { title: { ne: "" }, date: { ne: "" } } }
      sort: { fields: [frontmatter___date], order: DESC }
      limit: 5
      skip: 0
    ) {
      edges {
        node {
          fields {
            slug
          }
          frontmatter {
            title
            date(formatString: "DD MMMM YYYY, HH:mm", locale: "tr")
          }
          excerpt
        }
      }
      totalCount
    }
  }
`

Tarayıcınızı açıp http://localhost:8000 adresine yeniden bakarsanız, başlığın tıklanabilir hale geldiğini, başlığa tıklandığındaysa az önce derlediğimiz makale sayfasına ulaşılabildiğini göreceksiniz.

Aynı bağlantıyı, <Link> bileşenini yerine, <a> etiketi ile verdiğinizde ne değişiyor?

GatsbyJS’te Sayfalama Nasıl Yapılır?

Ne demiştik? Her şey için bir GatsbyJS eklentisi bulmak mümkün. Sayfalama istisna değil. Geliştirme sunucusunu kapatıp, “gatsby-paginate” paketini kuralım:

npm install gatsby-paginate

Ardından gatsby-node.js dosyasını yeniden açıp aşağıda gösterilen değişiklikleri yapalım:

const path = require('path')
const { createFilePath } = require('gatsby-source-filesystem')
const paginate = require('gatsby-paginate')

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `MarkdownRemark`) {
    const value = createFilePath({ node, getNode, trailingSlash: false })
    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions
  const postTemplate = path.resolve('./src/templates/PostTemplate.js')

  return new Promise((resolve, reject) => {
    resolve(
      graphql(`
        {
          posts: allMarkdownRemark(
            filter: { frontmatter: { title: { ne: "" }, date: { ne: "" } } }
            sort: { fields: [frontmatter___date], order: DESC }
            limit: 1000
          ) {
            edges {
              node {
                fields {
                  slug
                }
                frontmatter {
                  title
                  date(formatString: "DD MMMM YYYY, HH:mm", locale: "tr")
                }
                excerpt
              }
            }
            totalCount
          }
        }
      `).then(({ errors, data }) => {
        if (errors) {
          console.log(errors)
          reject(errors)
        }

        const { edges, totalCount } = data.posts

        edges.forEach(({ node: { fields: context } }) =>
          createPage({
            context,
            path: context.slug,
            component: postTemplate,
          })
        )

        paginate({
          createPage,
          edges,
          pageTemplate: './src/templates/IndexTemplate.js',
          pageLength: 5,
          context: {
            size: 5,
            totalCount,
          },
          // Aşağıdakiler opsiyonel. Varsayılanları böyle.
          pathPrefix: '',
          buildPath: (index, pathPrefix) =>
            index > 1 ? `${pathPrefix}/${index}` : `/${pathPrefix}`,
        })
      })
    )
  })
}

Ne yaptık? Eklentimizi paginate adıyla içe aldık ve sorgumuz çözümlendiğinde (resolve) kayıtlarımızı sayfalara bölmekte kullandık. Yanında kullanmak isteyebileceğimiz öğeleri context olarak eklentiye ilettik. Ayrıca, pageTemplate olarak IndexTemplate.js adında henüz bulunmayan bir dosyaya işaret ettik.

Şu an pages‘ın altında duran index.js‘i templates dizinine taşıyıp yeniden adlandıralım:

# Bu komut sadece Unix tabanlı işletim sistemlerinde çalışır.
# Komutun Microsoft karşılığı yanılmıyorsam "move".

mv src/pages/index.js src/templates/IndexTemplate.js

Ve açıp içeriğini şu şekilde değiştirelim:

import React from 'react'
import { Link } from 'gatsby'

import Layout from '../components/layout'

const createURL = (index, shift) =>
  index + shift === 1 ? '' : String(index + shift)

const nextLink = (Go, {index, next}) => (
  <Go to={createURL(index, +1)}>{next}</Go>
)

const prevLink = (Go, {index, prev}) => (
  <Go to={createURL(index, -1)}>{prev}</Go>
)

const pageLinks = (Go, { index, pageCount, next, prev }) => 
  index > 1
    ? index < pageCount
      ? (<div>
          {nextLink(Go, {index, next})}
          {prevLink(Go, {index, prev})}
        </div>)
      : (<div>{prevLink(Go, {index, prev})}</div>)
    : (<div>{nextLink(Go, {index, next})}</div>)

const PostNumbers = ({index, size, total}) => (
  <span style={{ marginLeft: '1ex' }}>
    [
    {Math.max(0, total - index * size) + 1}
    {' - '}
    {total - (index - 1) * size}
    ]
  </span>
)

const IndexPage = ({
  pageContext: {
    group,
    index,
    pageCount,
    additionalContext: {
      size,
      totalCount,
    },
  },
}) => (
  <Layout>
    <small>
      Toplam {totalCount} makale
      <PostNumbers
        index={index}
        size={size}
        total={totalCount}
      />
    </small>

    <ul>
      {group.map(({ node }) => (
        <li key={node.fields.slug}>
          <Link to={node.fields.slug}>
            <h2>{node.frontmatter.title}</h2>
          </Link>
          <small>{node.frontmatter.date}</small>
          <p>{node.excerpt}</p>
        </li>
      ))}
    </ul>

    {pageCount > 1 &&
      pageLinks(
        Link,
        {
          index,
          pageCount, 
          next: "◂ Eski Makaleler",
          prev: "Yeni Makaleler ▸"
        }
      )
    }
  </Layout>
)

export default IndexPage

// Gömülü sorguya artık gerek yok, onu silin.

Yukarıdaki kodu biraz açıklayayım:

  • createURL eklentimizin varsayılan buildPath ayarına uygun adresler üreten bir yardımcı fonksiyon.
  • nextLink, prevLink ve pageLinks, GatsbyJS’in Link‘ini alıp bulunulan sayfaya göre donatmaya yarayan üst seviye bileşenler (higher-order component).
  • PostNumbers listede kaçıncı makaleden kaçıncıya kadar gösterdiğimizi ekrana yansıtmakta kullandığımız basit bir bileşen.
  • data yerine pageContext‘e başvurduk, çünkü veriyi bu yolla sunan bir eklentiden alıyoruz artık.
  • Yeni eklentiden kaynaklı önemli bir değişiklik de şu: Listeleyeceğimiz bilgiler artık edges değil, group adıyla geliyor, ama içerik aynı.
  • Eğer sayfa sayımız 1‘den fazlaysa, bağlantı(lar) veriyoruz.

🔎 Dikkat ederseniz, sayfalamada yürütülen mantık ters çalışıyor. Sebebi, bloglarda makale sıralamasının yeniden eskiye doğru gitmesi. Bunu istediğiniz gibi yorumlayıp kendinize göre yeniden şekillendirebilirsiniz.

Makale sayınızı artırıp geliştirme sunucunuzu yeniden başlattığınızda ortaya çıkan sonuçsa şöyle bir şey:

GatsbyJS makale listelerinde sayfalama örneği

GatsbyJS Projenizi Nasıl Yayına Alırsınız?

Geliştirme sunucusunu kapatın. Terminalden ayrılmadan Gatsby CLI’ın kurulum esnasında package.json‘a yerleştirdiği build komut dizisini çalıştırın:

npm run build

Projemiz içerik fakiri olduğundan, kısa bir süre sonra derleme tamamlanacaktır. Her şey yolunda gittiyse, aşağıdaki tabloyla karşılaşacaksınız:

Gatsby projenizi basit bir komut dizisini çalıştırarak derleyebilirsiniz

Derleyici public adında bir dizin oluşturup, onun altında her yazıya ayrı HTML üretti ve sitenizi yapılandırdı. Artık, aşağıda gösterildiği gibi, bu dizine girip bir HTTP sunucusu başlatırsanız, derlenen siteyi gezebilirsiniz.

cd public && npx http-server

Şimdi tek yapmanız gereken, bu çıktıyı şunlardan birine yüklemek: 💁‍♂️

Bu kadar. Artık içeriğiniz yayında.

Kapanış

Sonunda içerisinde gezinebildiğimiz, makalelerimizi okuyabildiğimiz bir blog kurmayı başardık. Aferin bize. 💯 Gelgelelim, görsel düzenlemeler bir yana, çağdaş bir blog için bazı fonksiyonlar katmanız gerekecek:

  • Statik resim sunma (Evet, bunu anlatmadım. Belki başka yazıya…)
  • Sayfa üst bilgileri (meta data)
  • Site ikonu (favicon)
  • “Hakkımda”, “İletişim” gibi yardımcı sayfalar
  • Site içi arama
  • Ziyaretçi yorumları
  • Sosyal paylaşım tuşları
  • Arama motoru iyileştirmeleri (search engine optimization, SEO)
  • Metrik toplama (analytics)
  • Blogunuz yazılımla ilgiliyse, kod vurgulaması (highlight)

Bütün bunları yapmak zahmetli görünüyorsa, ki öyle, siz de benim bu blogda yaptığım gibi Gatsby başlangıç kitlerinden yararlanabilirsiniz. Bu makaleyeyse, hem kiti kendinize göre düzenlerken, hem de GatsbyJS’le başka işler yaparken başvurabilirsiniz.

Bitti. 🙏