Imported from ridhoadtyaa/QuranApp (
AGENTS.md). Install upstream withnpx skills add ridhoadtyaa/QuranApp. Copyright stays with the author.
AGENTS.md — Panduan Codebase Quran App
Dokumen ini adalah peta lengkap project untuk AI agent / developer baru. Dibuat berdasarkan pembacaan seluruh source code.
Ringkasan Project
Quran App adalah aplikasi web untuk membaca Al-Quran secara online (daftar 114 surah, detail ayat, terjemahan, tafsir, dan audio murottal). UI berbahasa Indonesia. Di-deploy di Vercel: https://quran-app-ran.vercel.app/
- Repo: https://github.com/ridhoadtyaa/QuranApp (author: Ridho Aditya Nurtama, lisensi MIT)
- Sumber data: eQuran.id API v2 — tidak ada database sendiri; semua data diambil saat build time (SSG).
- PWA: bisa di-install dan dipakai offline (via
next-pwa).
Tech Stack
| Kategori | Teknologi |
|---|---|
| Framework | Next.js 13.1.1 (Pages Router, bukan App Router) |
| Bahasa | TypeScript 4.6 (strict mode) |
| Styling | Tailwind CSS 3 (dark mode class) |
| UI primitives | Headless UI (Dialog/Transition untuk modal) |
| State global | Jotai (search untuk filter home; via atomWithStorage: lastRead, qari, arabicFontSize, showLatin, showTerjemahan) |
| Theme | next-themes (light/dark, disimpan di localStorage key theme) |
| SEO | next-seo + next-sitemap (sitemap & robots.txt digenerate saat postbuild). Pola judul "Nama Halaman — Quran App" via Seo.tsx; deskripsi unik per surah; JSON-LD WebSite (home) & BreadcrumbJsonLd (surah). SITE_URL selalu dinormalisasi tanpa trailing slash sebelum digabung slug — jangan gabung NEXT_PUBLIC_SITE_URL mentah (env-nya diakhiri /, hasilnya double slash) |
| HTTP | axios dan fetch (dipakai campur di getStaticProps) |
| Ikon | react-icons |
| Progress bar | nprogress (di route change; base CSS di-inline di globals.css) |
| Font | Poppins via @next/font (self-host, var --font-poppins di-set di _app.tsx); LPMQ (Arab) self-host WOFF2 + fallback TTF di public/fonts |
| PWA | next-pwa (nonaktif saat development; ada runtimeCaching StaleWhileRevalidate untuk equran.id/api) |
| Package manager | Yarn (ada yarn.lock) |
Perintah Penting
yarn dev # jalankan dev server (http://localhost:3000)
yarn build # production build + postbuild generate sitemap
yarn start # jalankan hasil build
yarn lint # next lint
yarn format # prettier --write
Environment variables (lihat .env.local, semua NEXT_PUBLIC_*):
NEXT_PUBLIC_SITE_NAME— "Quran App"NEXT_PUBLIC_SITE_URL— URL produksi (dipakai untuk canonical/OG URL)NEXT_PUBLIC_TWITTER_USERNAME— untuk meta Twitter card
Struktur Direktori
src/
├── pages/
│ ├── index.tsx # Home: daftar surah + search (getStaticProps, props dipangkas)
│ ├── surah/[id].tsx # Detail surah (getStaticPaths fallback:false + getStaticProps)
│ ├── _app.tsx # ThemeProvider + NProgress route events + @next/font Poppins
│ └── _document.tsx # lang='id', manifest PWA, favicon
├── components/ # pola Atomic Design
│ ├── atoms/ # Button, Input, UnstyledLink, BackToTop, Seo
│ ├── mollecules/ # (sic, typo dipertahankan) Surah, SurahInfo, Ayat, SearchBar, Modal, ToggleDarkMode, LastReadCard
│ ├── organism/ # Header, Footer, SurahDetail
│ └── templates/ # Layout (Seo + Header + main + BackToTop + Footer)
├── hooks/
│ ├── useTheme.tsx # wrapper next-themes + flag mounted (hindari hydration mismatch)
│ └── useWindowScroll.tsx# posisi scroll window (dipakai Header & BackToTop)
├── libs/
│ ├── twclsx.ts # twclsx = twMerge(clsx(...)) — utility className utama
│ ├── metaData.ts # getMetaData() → props untuk komponen Seo (OG, twitter, keywords)
│ └── index.ts # barrel export twclsx & getMetaData
├── stores/index.ts # Jotai atom: search (string, untuk filter surah di Home)
├── types/index.d.ts # declare module 'quran-app' — semua tipe respons API eQuran
└── styles/globals.css # Tailwind layers, class .layout/.star8/.next-before-button, @font-face LPMQ
public/
├── fonts/ # font LPMQ (teks Arab standar Kemenag)
├── manifest.json # PWA manifest
└── sw.js, workbox-*.js, sitemap*.xml, robots.txt # file GENERATED — jangan diedit manual
Alur Data (penting dipahami)
- Home (
/):getStaticPropsfetchhttps://equran.id/api/v2/suratlalu memangkas tiap surah ke 5 field yang dipakai kartuSurah(tipeSuratListItem;deskripsi&audioFulldibuang, payload ±120 KB → ±15 KB). Filter pencarian client-side terhadapnamaLatinvia atom Jotaisearch+useMemo. - Detail (
/surah/[id]):getStaticPathsgenerate 114 path dari daftar surat,fallback: false.getStaticPropshanya fetchsurat/{id}, lalu memangkasaudioper-ayat (6 qari, tidak dipakai UI) danaudioFullselain qari'01'. Tafsir juga sengaja TIDAK ikut di props statis (payload-nya besar, mis. Al-Baqarah ±650 KB) — di-fetch lazy di client olehSurahDetail.loadTafsir()saat user pertama kali membuka modal tafsir, lalu di-cache di state dan dipetakan keayat[i]berdasarkan index array.SurahDetailmerender ayat secara bertahap (chunk 30 ayat,AYAT_PER_CHUNK) memakai scroll listener + elemen sentinel, dan di-keypersurah.data.nomordi halaman agar state ter-reset saat pindah surah. Elemen<audio>memakaipreload='none'. - Ayat: tiap ayat menampilkan teks Arab (font
font-arabic/LPMQ), latin (viadangerouslySetInnerHTML, karena API mengirim HTML), dan terjemahan Indonesia. Tombol share memakai Web Share API (navigator.share) dengan URL anchor#nomorAyat; tombol tafsir memanggilloadTafsir()lalu membukaModal(tampil "Memuat tafsir..." selama data belum ada). Karena ayat dirender bertahap,SurahDetailmenangani anchor#nomorAyatsecara manual: saat mount, hash dibaca,visibleCountdinaikkan sampai ayat tujuan ikut dirender, laluscrollIntoView. - Audio: semua URL audio (murottal full & per ayat, 6 qari) dikonstruksi dari pola di
src/libs/audio.ts(cdn.equran.id/audio-{full|partial}/{slug}/{nomor}.mp3), bukan dari payload API. Playback per ayat dikelolaSurahDetaildengan satu instanceAudiobersama (playingAyatstate) — memutar ayat lain menghentikan yang sebelumnya, dan audio dihentikan saat unmount. Preferensi qari + tampilan (ukuran font Arab, toggle latin/terjemahan) diatur lewat modalReadingSettingsdi samping player. - Terakhir dibaca: tiap ayat punya tombol bookmark (toggle) yang menyimpan
{ surah, namaLatin, ayat }ke atomlastRead(atomWithStorage, localStorage keylast-read, per perangkat). Home menampilkanLastReadCard— shortcut ke/surah/{surah}#{ayat}yang memanfaatkan penanganan anchor diSurahDetail. Komponen yang membacalastReaddigate flagmountedagar bebas hydration mismatch (data hanya ada di client). - Tipe: semua bentuk respons API dideklarasikan di
src/types/index.d.tssebagai module ambient'quran-app'— import denganimport { SuratData } from 'quran-app'.
Konvensi Kode
- Path alias:
@/*→src/*(lihattsconfig.json). - className selalu lewat
twclsx(...), dengan class dikelompokkan per string berdasarkan concern ('flex items-center','py-4', dst). Ikuti pola ini saat menambah komponen. - Prettier (
.prettierrc.js): no semicolon, single quote (termasuk JSX), printWidth 100, no trailing comma, dan import diurutkan otomatis oleh@trivago/prettier-plugin-sort-importsdengan urutan:@/styles→@/components→@/*→ relatif → package eksternal, dipisah baris kosong. - ESLint (
.eslintrc.js):next/core-web-vitals+@typescript-eslint/recommended+ prettier;no-unused-varsdanno-explicit-any= error. - Komponen diekspor default, bertipe
React.FunctionComponent<Props>; komponen yang berat di-render dibungkusmemo(Ayat, SurahDetail, SurahInfo, ToggleDarkMode). - Dark mode: class-based. Warna utama
primary= paletpurpleTailwind; background dark =#001140(aliasdark). - Bahasa UI: seluruh teks yang tampil ke user berbahasa Indonesia.
- Aksesibilitas (dipertahankan saat mengubah UI): tiap halaman punya tepat satu
h1(logo header adalahspan, bukan heading); teks Arab wajiblang='ar' dir='rtl'; tombol ikon wajib punya<span className='sr-only'>+title; fokus keyboard memakaifocus-visible:ringglobal diglobals.css(jangan hapus); ada skip link "Langsung ke konten" diLayout→main#konten; modal punya tombol tutup terlihat daninitialFocuske tombol itu. Baseline: axe-core 0 violation di home & detail, kedua tema. - Commit: Conventional Commits (repo dikonfigurasi dengan commitizen
cz-conventional-changelog; riwayat commit memakai formatfeat:,fix:,chore:). Husky pre-commit menjalankan lint-staged (format/lint file yang di-stage).
Hal yang Perlu Diwaspadai (gotchas)
- Folder bernama
mollecules(typo dari "molecules") — jangan "memperbaiki" nama ini tanpa mengubah semua import. next-pwadisable saat dev; service worker hanya aktif di production build.next.config.jsmenghapus semuaconsole.*saat production (removeConsole).- Ada dua konfigurasi lint-staged (di
package.jsondan.lintstagedrc.js); yang dipackage.json(menjalankanyarn format) yang terbaca lebih dulu. - Tipe
SuratData/SuratDetail/Ayatlebih "lengkap" dari data runtime:getStaticPropsmemangkas field (lihat Alur Data).Ayat.audiobertipe opsional karena itu. Kalau butuh field yang dipangkas, tambahkan kembali secara sadar digetStaticProps— jangan asumsikan semua field API tersedia di client. - File di
public/sepertisw.js,workbox-*.js,sitemap*.xml,robots.txtadalah hasil generate build (sudah di-gitignore sebagian) — jangan diedit manual. - Pemetaan ayat↔tafsir mengandalkan urutan index array dari API; jika API berubah struktur, halaman detail bisa salah pasang tafsir.
- Tafsir di-fetch client-side langsung ke equran.id saat modal tafsir dibuka — kalau mengubah
SurahDetail, jaga agar fetch ini tetap sekali per surah (guardtafsirRequested) dan jangan kembalikan tafsir kegetStaticProps(payload Al-Baqarah membengkak ~1 MB). - Build membutuhkan koneksi internet ke equran.id karena semua halaman digenerate statis dari API.
- OG image dan favicon di-host eksternal di ImageKit (
ik.imagekit.io).