Promotional website for Airlines Group Travel hosted on static.app at https://airlinesgrouptravel.staticdomains.app. It includes:
- An SEO-ready static site: 33+ pages, schema, breadcrumbs, and a sitemap that builds itself
- A blog managed from an admin portal at
/admin/with a rich text editor - Lead capture: quote forms on the site, with leads viewable in the same admin portal
Phone number used site-wide: +1-888-609-1015 (edit it once in site.config.json).
/admin/ (rich text editor) ──commit──▶ GitHub repo (content/posts/*.json)
│ push / daily cron / "Rebuild" button
▼
GitHub Action: node scripts/build.mjs
(pages, blog, sitemap.xml, llms.txt, llms-full.txt, RSS, schema)
│
▼
static.app (designmodo/static-app-deploy-action)
Quote forms on site ──insert──▶ Supabase `leads` table ◀──read── /admin/ Leads tab
- No framework and no dependencies. The build is one Node script (
scripts/build.mjs) and runs in about 1 second. - Blog posts are JSON files in
content/posts/. The admin portal commits them through the GitHub API. Every commit triggers a rebuild and deploy, so new posts show up on the blog, in the sitemap, the RSS feed andllms-full.txtautomatically. - Scheduled posts: give a post a future publish date. The daily 06:00 UTC rebuild publishes it once the date passes.
- Leads go into a Supabase table. Row-level security means the public site can only insert leads, and only emails listed in
adminscan read them.
| Path | Purpose |
|---|---|
site.config.json |
Site URL, name, phone, email, address, GitHub repo, Supabase keys, analytics IDs |
scripts/content.mjs |
Copy for every static page (services, airlines, group types, FAQs, legal) |
scripts/build.mjs |
Static site generator: HTML, JSON-LD, breadcrumbs, sitemap, robots, llms, RSS, manifest |
scripts/serve.mjs |
Local preview server |
content/posts/*.json |
Blog posts (written by the admin portal) |
static/ |
Copied to the site root: favicons, uploads/ (blog images from the admin) |
src/assets/ |
CSS, JS, images |
src/admin/ |
Admin portal (blog CMS + leads) |
supabase/schema.sql |
Leads / admins / settings tables with row-level security |
.github/workflows/deploy.yml |
Build and deploy the site to static.app and the admin to GitHub Pages (on push, daily, or on demand) |
- Unique
<title>, meta description, canonical and robots tags on every page - Open Graph and Twitter cards (
summary_large_image) with a 1200×630 social image - JSON-LD schema:
TravelAgency/Organization,WebSite,WebPage,BreadcrumbList,Service,FAQPage,Blog,BlogPosting,ItemList,ContactPage,AboutPage - Breadcrumbs, both visible and in schema, on every inner page
sitemap.xmlgenerated automatically with reallastmoddates, plus an HTML/sitemap/pagerobots.txt(admin blocked, AI crawlers allowed),llms.txtandllms-full.txt(full site and blog text for AI assistants)- RSS feed (
/feed.xml), web manifest, favicons (.ico,.svg, Apple touch icon, 192/512 PNG),humans.txt,.well-known/security.txt - Blog: table of contents, reading time, related posts, prev/next links, lazy-loaded images, WebP image optimisation on upload
- Fast pages: no framework, one CSS file, system fonts, sticky click-to-call button on mobile
- Pages targeting long-tail searches: 10 airline group-booking pages and 6 group-type pages (corporate, sports, weddings, …)
- A 404 page and a noindexed thank-you page that works as a conversion goal
- Sign in at static.app and create a site (for example, upload the
site-distzip from the first GitHub Actions run, or any placeholder file). Name it airlinesgrouptravel. - Check the site's address. If it isn't
https://airlinesgrouptravel.staticdomains.app, updatesiteUrlinsite.config.json. Canonicals, the sitemap and schema all use this value. If you later connect a custom domain, changesiteUrlto it. - Copy the site's PID from Site → Settings → General.
- Create an API key from Account → API Keys. It starts with
sk_. API deploys require a paid static.app plan. - In this GitHub repo, go to Settings → Secrets and variables → Actions:
- Secret
STATIC_APP_API_KEY= yoursk_…key - Variable
STATIC_APP_PID= the site PID
- Secret
- Push a commit or run the workflow manually (Actions → Build & deploy → Run workflow).
Until both are set, the workflow still builds the site and attaches it as a downloadable site-dist artifact, which you can upload to static.app by hand.
The admin lives at https://alphaneeraj.github.io/AGT-Static-App/admin/. /admin/ on the site redirects there.
Why GitHub Pages? static.app sends
Content-Security-Policy: connect-src 'self' https://*.static.domains https://static.app, which stops pages on the site from callingapi.github.comor Supabase from the browser. The build therefore writes the admin todist-admin/, and the workflow publishes it to GitHub Pages. For the same reason, quote forms send leads as a normal form POST into a hidden iframe (url-encoded, to PostgREST) instead of usingfetch(). Google Analytics requests may be blocked by the same policy.
It needs a GitHub fine-grained personal access token: github.com/settings/personal-access-tokens/new
- Repository access: Only select repositories → AGT-Static-App
- Permissions: Contents: Read and write, Actions: Read and write
Before Supabase is set up, you sign in to the admin by pasting this token. After Supabase is set up, you sign in with email and password, and the token is saved once under Settings and shared by all admins.
- Create a project at supabase.com.
- Open the SQL Editor, paste in
supabase/schema.sql, and run it. - Under Authentication → Users → Add user, create your admin login (email + password, auto-confirm).
- Add that email to the admin list in the SQL Editor:
insert into public.admins (email) values ('you@example.com');
- Under Authentication → Sign In / Providers, turn off Allow new users to sign up.
- Copy Project URL and the anon public key from Project Settings → API into
site.config.json:The anon key is meant to be public. Row-level security is what protects the leads."supabase": { "url": "https://xxxx.supabase.co", "anonKey": "eyJ..." }
- Commit and push. Quote forms now save to Supabase, and leads appear under Admin → Leads. From there you can search them, filter by status (new / contacted / quoted / booked / closed / spam), add notes, delete them, and export to CSV.
Until Supabase is configured, forms carry static.app's static-form attribute as a fallback, so submissions can still reach your static.app dashboard if static.app forms are enabled on your plan.
Spam protection: a hidden honeypot field, a minimum time before submitting, and length checks in the database.
- Submit
https://<site>/sitemap.xmlin Google Search Console and Bing Webmaster Tools. - Put the verification codes in
googleSiteVerification/bingSiteVerification, and a GA4 ID ingaId, insidesite.config.json. The form sends agenerate_leadevent.
npm run dev # build + serve at http://localhost:8080
npm run build # build only → dist/Node 18 or newer. No npm install needed.
- Phone, email, address, site URL:
site.config.json - Page copy, FAQs, airlines, group types:
scripts/content.mjs. SetpagesUpdatedin the config to today's date when you change it, so the sitemaplastmodstays accurate. - Blog: use
/admin/, or add or edit JSON files incontent/posts/by hand:{ "title": "…", "slug": "…", "status": "published", "date": "2026-10-01T09:00:00Z", "tags": ["Group Travel"], "metaDescription": "…", "cover": "/uploads/…", "content": "<p>…</p>" }
- The footer, About page and airline pages state that AGT is an independent agency not affiliated with any airline. Airline names are used descriptively, and no airline logos are included.
- No reviews, ratings or certifications are claimed. If you hold IATA / ARC / ASTA accreditation, add it to
scripts/content.mjsalong with your accreditation numbers.