TL;DR: I built Mashit30.co.il, a Hebrew right-to-left study and practice website for Israeli skipper (30 משיט) certification exams, using Astro, React, TypeScript, Firebase and an AI coding assistant. It has 733 practice questions, offline-safe progress sync, a PWA, and a full SEO setup. This post covers the stack, the stages, the AI workflow, and what I would do differently.
Table of contents
- What is Skipper30?
- Tech stack
- AI models and effort
- Build stages
- Key engineering decisions
- SEO for a Hebrew, login-gated app
- Deployment
- Lessons learned
- FAQ
What is Skipper30?
Skipper30 helps people prepare for the Israeli skipper license exams. It combines short course pages with interactive practice banks:
- 733 questions across engine (178), instrument navigation (193) and seamanship (362)
- Four study tracks, including coastal navigation
- Category filters, mastery tracking, achievement badges and exam simulations
- Google sign-in with progress synced across devices
- Installable on a phone as a PWA
- Native Hebrew RTL layout with light and dark themes
The goal was simple: a fast, free, distraction-free way to drill exam questions on a phone.
Tech stack
| Layer | Choice | Why |
|---|---|---|
| Framework | Astro 7 | Static output, great Core Web Vitals, MDX for course content |
| UI islands | React 19 | Interactive practice and exam components only where needed |
| Language | TypeScript 6 | Typed question banks and progress logic |
| Content | MDX | Authored Hebrew course pages with components |
| Auth and data | Firebase Authentication + Cloud Firestore | Google sign-in and per-user progress |
| Validation | Zod | Schema checks for synced data |
| Icons | lucide-react | Light, tree-shakable |
| Unit tests | Vitest | Progress, exam and image-size logic |
| E2E tests | Playwright | Responsive layout and flow checks |
| Hosting | nginx in Docker on a VPS behind Traefik | Full control of headers, caching and charset |
| CI/CD | GitHub Actions + GHCR | Build image, push, deploy over SSH |
| SEO | @astrojs/sitemap, JSON-LD | Sitemap, structured data, canonical URLs |
Astro was the key choice. Most of the site is static HTML, and React hydrates only the practice and exam screens. That keeps pages light, which helps both users on mobile data and search rankings.
AI models and effort
I built the project in an AI pair-programming workflow with GitHub Copilot, working from a git worktree per task and reviewing every change before merging.
| Item | Value |
|---|---|
| Tooling | GitHub Copilot (agent mode, CLI and app) |
| Models used | GPT 5.6 Sol, Opus 5.5, Sonnet 5.5 |
| Reasoning effort | medium |
| Calendar time | About 3 weeks |
| Active hands-on time | 8 hours |
| Commits | 88 |
| Code size | About 9.7k lines of TypeScript, TSX, Astro and MDX |
How I split work between models and effort levels:
- Higher effort for architecture, sync and offline-queue logic, security headers, and anything with subtle edge cases.
- Lower effort for copy tweaks, styling fixes, data cleanup and repetitive refactors.
- Always reviewed by me: every diff, plus tests, before it reached
main.
The biggest productivity win was not raw code generation. It was fast iteration: describe a bug in plain language, get a fix and a test, verify in the browser, move on.
Build stages
Stage 1: Foundation and first practice bank (Sep 18)
Astro project, RTL layout, theming, the engine practice course, GitHub Pages deploy and the first README. Smart repetition and the mastery rule (three correct answers in a row) came from this stage.
Stage 2: More courses and gamification (Sep 18 to 20)
Instrument navigation and seamanship banks, category progress, 24 achievement badges per bank, and mobile layout polish. I also added Firebase auth and Firestore sync here.
Stage 3: Sync, PWA and exam simulations (Sep 21 to 22)
Offline queue with duplicate protection, real-time sync fixes, installable web app support, Android status bar theming, randomized answer order and timed exam simulations for engine, navigation and seamanship.
Stage 4: Content and video libraries (Sep 22 to 25)
Question banks finalized, curated video playlists per course, redesigned footer and course pages.
Stage 5: QA and mobile UX (Oct 3)
A dedicated QA pass: Hebrew spacing and parentheses, degree symbols, composite answer options kept in order, a stuck loading state fixed, and compact mobile layouts.
Stage 6: SEO, public question pages and hardening (Oct 4)
The biggest day. Sitemap, robots, JSON-LD, keyword research validated with Google Autocomplete, public per-question pages with an interactive answer check, security headers and CSP, PWA installability fixes on Android and richer homepage content.
Stage 7: Production deploy (Oct 4)
Docker image, GitHub Container Registry, SSH deploy to a VPS behind Traefik, custom domain, plus a mirror on GitHub Pages.
Stage 8: Learning from mistakes (Oct 6)
Wrong-answer tracking, accuracy stats and a mistakes review mode, then mobile fixes and tests.
Key engineering decisions
Local-first progress. Every answer updates the UI immediately and is queued locally. Firestore sync happens in the background, with timeouts, retry and duplicate-operation protection. Studying on a boat or in a spot with weak signal still works.
Smart repetition without a heavy algorithm. Questions are weighted toward unmastered and never-correct items, never repeat back to back, and mastered items leave the pool until the whole bank is mastered.
Islands, not a SPA. Only interactive screens ship JavaScript. Course pages and public question pages are plain static HTML.
Security as a feature. Firestore rules restrict data to the owning user, and nginx serves a strict CSP and security headers.
SEO for a Hebrew, login-gated app
A login-gated app cannot rank, so I split the site into indexable and non-indexable parts:
- Indexable: home, four course pages and public per-question pages (the long-tail opportunity).
- Not indexable: practice and exam pages behind login, marked
noindex.
What I did:
- Keyword research with Google Autocomplete (hl=he, gl=il) to confirm real demand for Hebrew phrases, then mapped each keyword to one target page.
- Unique titles, descriptions and H1s per page, using the dominant Hebrew spelling and mentioning variants in body text.
- Structured data (JSON-LD): WebSite, WebApplication, Course, Quiz and FAQPage.
- Canonical URLs, Open Graph and Twitter cards, plus a generated sitemap and robots.txt.
- Internal linking from course pages into question pages and back.
- Performance: static output, image dimensions set to avoid layout shift, optimized font loading.
- Server details: correct charset,
server_tokens off, andapplication/manifest+jsonfor the web manifest. - Richer homepage content with a guide section and FAQ, rewritten in original wording to avoid thin or duplicated copy.
Tip for any multilingual or RTL site: set lang and dir correctly, test social previews, and check how Google renders your pages in Search Console.
Deployment
Pushes to main trigger GitHub Actions, which builds a Docker image (nginx serving dist/), pushes it to GitHub Container Registry and deploys over SSH to a VPS behind Traefik. A second workflow mirrors the site on GitHub Pages. Firebase config is injected as public build variables, and the domain is added to Firebase authorized domains.
Lessons learned
- Write the rules down early. A short instructions file for the AI (lockfile rules, preview URL quirks) saved many repeated corrections.
- Tests make AI changes safe. Unit tests for progress logic and Playwright for layout caught regressions fast.
- Hebrew RTL needs real QA. Parentheses, degree signs and mixed-direction text break in subtle ways. Check on real devices.
- Do SEO before launch, not after. Public question pages gave the site a long-tail surface that login-gated pages never could.
- Keep scope honest. The mistakes review mode came from using the app myself, not from a plan.
FAQ
Can you really build a production site with an AI coding assistant?
Yes, but treat the AI like a fast junior pair: give clear tasks, review every diff and keep tests green.
Why Astro instead of Next.js?
Most pages are static and content-driven. Astro ships less JavaScript by default and has first-class MDX.
How does offline progress sync work?
Answers apply locally first and are queued. When connectivity returns, they sync to Firestore with duplicate protection.
Is the site free?
Yes. Visit Mashit30.co.il and sign in with Google to track progress.