Tutorial that I used to build this website — Blog by Muhammad Daffa Ashdaqfillah

Dokumentasi tutorial2 yg membantu untuk membuat web ini 🎨😅

Tutorial that I used to build this website — Blog by Muhammad Daffa Ashdaqfillah

Published on June 3, 2024

Article Details

Haii, sebenernya ini ringkasan tutorial umum yang aku pakai aja buat develop website ini, ini bisa ditemui banyak di youtube atau artikel. tp mungkin di public referensi2 nya terpisah pisah-pisah, nah di sini aku mau menyatukan aja gimana caranya deploy web statis ini tp seolah seperti web dinamis (update data dari notion jd tidak perlu selalu berurusan dengan kode dan publish ulang, bisa pake routing tanpa terbatas oleh spa-single page applications, sama pake custom domain).

Hanya modal waktu aja sih, ga ada keluar material sepersenpun hehe..

Poin poin nya mulai dari:

  • Domain .me gratis setahun bagi yang punya github student developer pack
  • Setup untuk customize UI dengan shadcn sama aceternity
  • Setup API Notion Database
  • Setup Fetch Data Next.js
  • Notion page renderer untuk menampilkan page seperti blog dan detail project di web ini
  • Cara deployment github pages, serta cara routing react di spa (single-page applications), karna github pages ini sifatnya memang spa, hal ini perlu dilakuin supaya orang bisa akses halaman spesifik kamu hanya melalui direct link, jadi tidak perlu dari halaman awal untuk aksesnya, sama seperti kalian kalau akses halaman blog ini dari link 😅

Tech Stack

Di sini aku menggunakan tech stack seperti ini:

  • React.js (vite-deploy di github pages) untuk Front-end
  • Next.js (vite-deploy di vercel) untuk Fetch data API Notion
  • Notion untuk menyimpan data yang ditampilkan di web
  • Shacdn UI dan Aceternity UI untuk components
  • Typescript untuk penulisan kode

Kenapa pakai React.js dan Next.js, karna init project ini awalnya pakai React, sudah jalan cukup banyak, pas butuh fetch API dari Notion ternyata bisanya di Next.js karna permasalahan CORS Policy. Sebenarnya lebih ke malas refactor ke Next sama mungkin kalau Next di vercel kali ya deploynya bukan di ghpages, jujur ilmuku dikit, blum paham-paham bgt kek ginian :)) jadi ya udah aku campur aja tampilannya pake react, fetch api nya pake next. Tp saran sih mungkin bisa langsung ke Next.js aja sekalian sih.. karna sebenernya semua library dan permasalahan yg aku temui larinya ke Next.js :) tp ak blum ada niat untuk refactor web ini ke sana


Setup Project React js dan Deployment gh-pages

Ikutin tutorial ini aja~


Setup Domain .me (khusus student developer pack)

Jadi Github Student Developer Pack punya previllage untuk bisa custom domain .me menggunakan namecheap, kamu bisa lihat tutorial ini aja

atau bisa langsung akses dari page Github Student Developer Pack, lalu cari namecheap

GitHub Student Developer Pack

GitHub Student Developer Pack

The best developer tools, free for students. Get your GitHub Student Developer Pack now.

https://education.github.com/pack?sort=popularity&tag=Developer+tools

disitu kamu bisa dapat domain .me secara gratis tapi satu tahun aja

Lalu jangan lupa update domain di github pages nyaa dengan domain tadi

Image

untuk artikel lebih lanjut bisa dari ini

How to link your Namecheap domain to GitHub Pages

How to link your Namecheap domain to GitHub Pages

🌐 Namecheap Sign in to your Namecheap account: Select Domain List from the left sidebar...

https://dev.to/fabriziobagala/how-to-link-your-namecheap-domain-to-github-pages-49a0


Setup Shadcn UI dan Aceternity UI

bisa langsung lihat docs nya aja, klo instalasi kedua ini memang disarankan pake Next langsung aja sih, karna kedua libary ini kiblatnya Next js, jd klo di pake di React bisa bisa aja, tp ada sedikit2 penyesuaian gitu nnti setiap mau make komponennya kayak <Link/> dsb.

Introduction

Introduction

Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.

https://ui.shadcn.com/docs

Install Next.js

Install Next.js

Install Next.js with Create Next App

https://ui.aceternity.com/docs/install-nextjs

Kalau udah setup tinggal make komponennya aja:

  • shadcn tinggal install aja lewat terminal dan bisa langsung diimport
  • aceternity perlu buat file dulu lalu copy code nya di components, baru bisa diimport

Desain Tampilan Web

Desain web nya dulu, baru kalau komponennya udah siap bisa ke Fetch API Notion Database, sama sekalian siapin kontainer untuk data yang mau difetch lewat API Notion

Kalau belum familiar dengan desain web pakai framework, bisa ikutin tutorial project2 langsung aja dari youtube banyakk bgt


🛠️ Fetch Notion API Database (Opsi 1)

Ini tutorial yang aku temuin setelah selesai setup project ini dan kelihatannya sih lebih gampang dari yang aku pake sekarang, kalian bisa pakai worker ini aja, karna mungkin kalau dari react ga perlu pakai setup Next js https://github.com/splitbee/notion-api-worker


Fetch Notion API Database (Opsi 2)

Nah kalau yang ini yang aku terapin sekarang karna nemu tutorial nya duluan yang ini 😅

Buat Notion Integration ke Next js, bisa ikuti tutorial ini ~

Kalau udah bisa jalan di localhost, tinggal di deploy ke vercel

Note

  • Kalau full makai Next js, langsung implementasi frontendnya aja di sana, tinggal atur return api nya apa dan dipakai di pages mana.
  • Kalau pakai React buat tampilan dan Next js buat fetch, di Next js nya cuma berurusan di folder API ajaa, nanti dari React fetch data ke vercel nyaa

Notion Page Renderer (seperti blog ini)

Nah kalau kalian pengen buat web yang ada Blog nya seperti ini, atau di portfolio kalian pengen ada detail Project nya atau dokumentasinya, bisa banget pakai ini, karna konsepnya ini ambil data pages dari notion untuk ditampilin di web, kalian bisa ikuti dokumentasinya di link github di bawah, di situ juga ada contoh json serta pengolahan fetch nya https://github.com/splitbee/react-notion


Deployment Next JS (kalau pake opsi 2 untuk fetch Notion)

Ikutin tutorial ini aja untuk deploy next.js ke vercel

Jangan lupa untuk simpan ENV kalian seperti secret dan database_id di setting vercelnya


Berikut contoh implementasi API yang aku gunain untuk blog ini

Bentuk Notion Databasenya

Handler API Next js

TypeScriptimport { Client } from "@notionhq/client";
import type { NextApiRequest, NextApiResponse } from "next";
import fetch from 'node-fetch';

// simpan di env kalian di setting vercel
const notionSecret = process.env.NOTION_SECRET;
const notionDatabaseId = process.env.NOTION_DATABASE_ID_BLOG;

const notion = new Client({ auth: notionSecret });

type Row = {
  id: string;
  properties: {
    date: { id: string; date?: { start: string } };
    Name: { title: { plain_text: string }[] };
    Deskripsi: { rich_text?: { plain_text: string }[] };
    img: { files?: { file?: { url: string } }[] };
  };
};

type rowsBlog = {
  id: string;
  title: string;
  deskripsi: string;
  img_url: string;
  date: string;
};

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (!notionSecret || !notionDatabaseId) {
    throw new Error("Notion secret or database id not found");
  }

  try {
    const response = await notion.databases.query({
      database_id: notionDatabaseId,
    });

    const rows = response.results as unknown as Row[];

		// untuk mapping ke properties Blog
    const filteredData: rowsBlog[] = rows.map((row) => ({
      id: row.id || "",
      title: row.properties.Name.title[0].plain_text || "",
      deskripsi: row.properties.Deskripsi.rich_text?.[0]?.plain_text || '',
      img_url: row.properties.img.files?.[0]?.file?.url || '',
      date: row.properties.date?.date?.start || "",
    }));

		// untuk mendapatkan konten page Blog nya
    const detailedData = await Promise.all(filteredData.map(async (page) => {
      const additionalData = await fetch(`https://notion-api.splitbee.io/v1/page/${page.id}`).then(res => res.json());
      return {
        ...page,
        properties: additionalData
      };
    }));

    res.setHeader('Access-Control-Allow-Origin', '*');
    return res.status(200).json(detailedData);
  } catch (error) {
    console.error("Error fetching data from Notion API:", error);
    return res.status(500).json({ error: 'Failed to fetch data from Notion API' });
  }
}

Handler di Page React js

TypeScriptinterface BlogPost {
  title: string;
  description: string;
  date: string;
  image: string;
  onClick: () => void;
}

interface Blog {
	// attribut blog
  id: string;
  title: string;
  deskripsi: string;
  img_url: string;
  date: string;
  // page blog nya
  properties: BlockMapType;
}

function Blog() {
  const [data, setData] = useState<Blog[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const location = useLocation();
  const isBlogRoute = location.pathname === "/blog";
  const navigate = useNavigate();

  useEffect(() => {
    const fetchData = async () => {
      try {
	      // alamat API next js
        const response = await axios.get(
          "https://be-daf2a.vercel.app/api/notion-blog"
        );

        if (response.status !== 200) {
          throw new Error("Notion API request failed");
        }

        const sortedData = sortByDate(response.data);
        setData(sortedData);
      } catch (error) {
        console.error("Error fetching Notion data:", error);
      } finally {
        setIsLoading(false);
      }
    };

    fetchData();
  }, []);
	
	// sorting date
  const sortByDate = (data: Blog[]): Blog[] => {
    return data.sort(
      (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
    );
  };

  const formatDate = (dateString: string) => {
    const options: Intl.DateTimeFormatOptions = {
      year: "numeric",
      month: "long",
      day: "numeric",
    };
    return new Date(dateString).toLocaleDateString(undefined, options);
  };

untuk menampilkan halaman blog nya bisa langsung pakai ini, sesuai yang ada di tutorial Notion Page Renderer https://github.com/splitbee/react-notion

TypeScript<NotionRenderer blockMap={blog.properties as BlockMapType} />

Tutorial Routing React.js di spa (single-page applications)

Tutorial videonya

Code nya

404.html

TypeScript<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>React Router</title>
    <script type="text/javascript">
      var pathSegmentsToKeep = 1;

      var l = window.location;
      l.replace(
        l.protocol +
          "//" +
          l.hostname +
          (l.port ? ":" + l.port : "") +
          l.pathname
            .split("/")
            .slice(0, 1 + pathSegmentsToKeep)
            .join("/") +
          "/?/" +
          l.pathname.slice(1).split("/").slice(pathSegmentsToKeep).join("/").replace(/&/g, "~and~") +
          (l.search ? "&" + l.search.slice(1).replace(/&/g, "~and~") : "") +
          l.hash
      );
    </script>
  </head>
  <body></body>
</html>

script di head index.html

TypeScript<script type="text/javascript">
  (function (l) {
    if (l.search[1] === "/") {
      var decoded = l.search
        .slice(1)
        .split("&")
        .map(function (s) {
          return s.replace(/~and~/g, "&");
        })
        .join("?");
      window.history.replaceState(null, null, l.pathname.slice(0, -1) + decoded + l.hash);
    }
  })(window.location);
</script>

Untuk referensinya bisa pakai dokumentasi github ini

https://github.com/rafgraph/spa-github-pages

Note: untuk yang menggunakan custom domain seperti .me jangan lupa mengganti var pathSegmentsToKeep = 1; di 404.html menjadi 0