# 🚀 Membangun Website Pribadi Dinamis dengan GitHub Pages dan Google Sheets API. Studi Kasus: mustofin.com

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Bagaimana aku membuat website pribadi yang gratis, cepat, dan tetap bisa diperbarui otomatis — tanpa backend dan tanpa hosting berbayar?</div>
</div>

Sebagai seorang profesional di bidang teknologi dan manajemen, serta memiliki ketertarikan di bidang pendidikan, aku ingin punya website pribadi yang dapat menampilkan data profil dan portfolio yang selalu mutakhir, tanpa perlu login ke CMS atau menyentuh kode setiap kali memperbarui informasi.

Akhirnya, tercetus proyek sederhana namun efisien:

> [mustofin.com](http://mustofin.com) — website statis dengan data dinamis dari Google Sheets API

## 🧩 Arsitektur Solusi

```plaintext
Google Sheets (data profil & portfolio)
         ↓ API via Google Apps Script
Website Statis (HTML + Tailwind + JS)
         ↓
Hosted on GitHub Pages
```

🟢 **GitHub Pages** digunakan sebagai hosting statis — gratis, cepat, dan mudah di-deploy langsung dari repository.  
🟢 **Google Sheets** berperan sebagai “database ringan” yang bisa diperbarui kapan saja.  
🟢 **Google Apps Script** menyediakan endpoint API untuk mengubah data Sheet menjadi JSON.  
🟢 **Vanilla JavaScript + TailwindCSS** untuk menampilkan data di halaman web.

## ⚙️ Langkah Implementasi

### 1️⃣ Merancang Tampilan Web Profil & Portofolio

Pertama yang dilakukan adalah membuat struktur dan tampilan website utama.

```plaintext
/index.html          → Beranda dan hero section
/about.html          → Profil & pengalaman (CV Online) → diambil dari Google Sheets)
/portfolio.html      → Daftar proyek dan hasil karya → diambil dari Google Sheets
/blog                → redirect ke hashnode
/community.html      → Keterlibatan di komunitas
/class.html          → Kelas & Produk Digital → embed mayar.id
/contact.html        → Kontak email dan sosial media
```

Tampilan menggunakan TailwindCSS agar tetap ringan tanpa framework besar.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760579205757/627cf401-ccc1-42be-840d-0a2dba527f56.png align="center")

### 2️⃣ Merancang Google Sheet sebagai Database

Membuat satu Google Sheet dengan beberapa sheet (tab) seperti: Portfolio, Profile, Experience, Education, Licenses, Skills, dan Master.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760579412490/903fab53-add2-4091-a2c1-2f8d881a6f3b.png align="center")

### 3️⃣ Membuat API dari Google Apps Script

Selanjutnya, menulis Google Apps Script untuk membaca data dari Sheet dan mengembalikannya dalam format JSON.

```javascript
function doGet(e) {
  try {
    const page = e.parameter.page;
    let result;
    if (page === 'about') {
      result = getProfileData();
    } else {
      result = getPortfolioData(e);
    }
    
    return createJsonResponse(result);

  } catch (error) {
    return createJsonResponse({ error: "An unexpected error occurred.", details: error.message }, 500);
  }
}
```

Kemudian:

1. Klik **Deploy → New Deployment → Web App**
    
2. Pilih **Execute as:** *Me (your account)*
    
3. Pilih **Who has access:** *Anyone*
    
4. Klik **Deploy** dan salin URL API seperti:
    

```bash
https://script.google.com/macros/s/.../exec
```

### 4️⃣ Menampilkan Data ke Website

Di halaman `about.html` dan `portfolio.html` ditambah kode JavaScript untuk memanggil API dari Google Apps Script tadi, lalu menampilkan hasilnya ke halaman web.

```javascript
function fetchFromAPI() {
                grid.innerHTML = `<div class="loader-container col-span-3"><div class="loader"></div><p class="text-center text-gray-500">Memuat portofolio...</p></div>`;

                fetch(apiUrl)
                    .then(response => {
                        if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
                        return response.json();
                    })
                    .then(response => {
                        if (response.status === 'success' && response.data.length > 0) {
                            // Simpan data dan timestamp ke cache
                            const itemToCache = {
                                data: response.data,
                                timestamp: new Date().getTime()
                            };
                            localStorage.setItem(CACHE_KEY, JSON.stringify(itemToCache));
                            
                            // Inisialisasi halaman dengan data baru
                            initializePage(response.data);
                        } else {
                            grid.innerHTML = '<p class="col-span-3 text-center text-gray-500">Tidak ada data portofolio untuk ditampilkan.</p>';
                            filterContainer.style.display = 'none';
                        }
                    })
                    .catch(error => {
                        console.error('Error fetching portfolio data:', error);
                        grid.innerHTML = '<p class="col-span-3 text-center text-red-500">Gagal memuat data. Silakan coba muat ulang halaman.</p>';
                        filterContainer.style.display = 'none';
                    });
            }
```

### 5️⃣ Membuat Fitur Tambahan: Download CV Otomatis

Fitur terakhir yang dibuat adalah fitur **Generate CV** berdasarkan data profil yang sudah update di google sheet. Fitur dipasang langsung di file Google Sheet karena ada data sensitif yang akan tampil di CV hasil generate.

Hal pertama yang harus dilakukan untuk fitur ini adalah membuat **template CV menggunakan google doc.**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760580140757/18e30009-b3a3-4be1-8395-82740693674e.png align="center")

Lalu membuat google apps script untuk generate CV

```javascript
function generateCvPdf() {
  const data = getProfileDataFull();
  const { profile, experience, education, licenses, skills } = data;

  experience.reverse();
  education.reverse();

  const templateFile = DriveApp.getFileById(CV_TEMPLATE_ID);
  const newFileName = `CV - ${profile.full_name || 'Resume'}.pdf`;
  const outputFolder = DriveApp.getFolderById(FOLDER_OUTPUT_ID);

  // Hapus file lama jika ada dengan nama yang sama agar tidak menumpuk
  const oldFiles = outputFolder.getFilesByName(`${newFileName}.pdf`);
  while (oldFiles.hasNext()) {
    oldFiles.next().setTrashed(true);
  }

  const tempFile = templateFile.makeCopy(newFileName);
  const docId = tempFile.getId();
  
  try {
    const doc = DocumentApp.openById(docId);
    const body = doc.getBody();

    // 1. Ganti placeholder simpel (Profile)
    for (const key in profile) {
      body.replaceText(`{{${key}}}`, profile[key] || '');
    }

    // 2. Ganti placeholder daftar (Skills)
    const skillsText = skills.map(s => s.skill_name).join(' • ');
    body.replaceText(`{{skills}}`, skillsText);
    
    // 3. Proses bagian berulang dengan data yang urutannya sudah benar
    processParagraphSection(body, experience, 'exp', 'start_date', 'description'); 
    processParagraphSection(body, education, 'edu', 'institution_name', 'end_year');
    processTableSection(body, licenses, 'lic', 'certification_name');
    
    // Hapus placeholder yang tidak terisi agar bersih
    body.replaceText("\\{\\{.*?\\}\\}", "");

    doc.saveAndClose();

     // Buat file PDF di folder tujuan
    const pdfFile = outputFolder.createFile(tempFile.getAs(MimeType.PDF)).setName(`${newFileName}.pdf`);
    return pdfFile;
  } catch (error) {
    Logger.log(`Gagal membuat PDF: ${error.toString()}\n${error.stack}`);
    return ContentService.createTextOutput(`Gagal membuat PDF: ${error.toString()}`);
  } finally {
    // Pastikan file sementara selalu dihapus
    DriveApp.getFileById(docId).setTrashed(true);
  }
}
```

Fitur dipanggil melalui custom menu di google sheet, agar hanya kita yang memiliki akses.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760580346853/883ec336-89be-4bdb-874a-cb4694bac95f.png align="center")

### 6️⃣ Deployment

Untuk deploy website, menggunakan github page agar gratis, lalu beli domain `mustofin.com` untuk custom domain dari github page.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760580551841/0eddfb5f-644b-4416-9e45-f5cf6bb13fc5.png align="center")

## 💡 Hasil Akhir

Dengan kombinasi GitHub Pages + Google Sheets API, akhirnya berhasil membangun:

* 🌐 **Website profesional** dengan domain custom ([`mustofin.com`](http://mustofin.com))
    
* ⚙️ **Konten dinamis** (profil & portofolio) tanpa CMS
    
* 💰 **Tanpa biaya hosting**
    
* 💡 **Low maintenance**, mudah diperbarui langsung dari Google Sheets
    

> Bangun solusi kecil yang memberikan nilai nyata dan dapat berkembang secara iteratif.
