I built a Quran audio project where the same JavaScript generates both a static website and an Android app, with no runtime dependencies in the generator. Here is how it is structured and what I would do again.

The result is called Suwar: one page per surah, in Arabic and English.

The constraint

I wanted every one of the 114 surahs to have its own indexable page, in two languages, plus a mobile app that reuses the same interface. That is 228 pages. I did not want a framework build step to maintain for something this simple.

The structure

  • Data: a fetch script pulls the text once and validates it (114 surahs, 6,236 verses) before anything is generated. If the counts are off, the build stops.
  • Generator: plain Node.js, no dependencies. It writes one HTML file per surah per language, plus a sitemap, hreflang alternates, JSON-LD, and an llms.txt file.
  • App: Capacitor wraps the same built UI as an Android project. The audio player uses MediaSession (repeat, speed, sleep timer, resume).
  • Tests: run with Node and a headless Chromium, so the generator can be checked without an Android SDK.

What worked

  1. Validating data before generating. A wrong verse count fails the build instead of shipping bad pages.
  2. Keeping the generator dependency-free. It runs anywhere Node runs, which mattered because I develop on a machine where package installs are not always possible.
  3. One UI for web and app. Fixes land once.

What I would watch for

  • Attribution and licensing: the text comes from Tanzil under CC BY 3.0, so the source must be credited and the text must not be modified.
  • Audio rights are separate from text rights. Each reciter is configured with a licensed flag so that any of them can be removed at once.
  • Ads: a small banner on regular screens and an optional rewarded ad. None during playback.

If you build content sites with many near-identical pages, the validate-then-generate pattern is the part worth copying.