Building a voice English-practice app in the browser (what broke first)
I'm Gabriel Luis Pacheco, founder of BataTalk — a Brazilian product for spoken English practice with AI characters and an in-scene coach (Tato). This is a build post, not a growth flex. The product in one breath BataTalk helps Brazilians who understand English but freeze when it's their turn to speak. Practice happens by voice in the browser: AI characters in everyday scenes (café, airport, interview), and Tato cues the start of the sentence when you blank. Levels A1–C2. Free plan: one guided conversation per day, no card. Paid plans are priced in BRL on /planos. Honest limits: AI can err; not a certificate; not a live teacher; no accent score theater. Stack in one paragraph Node 24+, vanilla JS for the app shell, marketing pages SSR'd as HTML, Supabase Auth + Postgres (service role on the API, RLS as second layer), Stripe, Resend, Cloudflare Turnstile, Render + Cloudflare. Voice path: browser mic → our API → transcription / model reply. We intentionally do not store the audio file. Constraint that shaped everything The target user will not "just prompt ChatGPT every day." So the product ships scenes with goals and a coach for the freeze moment. That kills tempting features: social feed, streak theater, fake certificates, pronunciation grades based on flaky ASR text. Browser-first matters in Brazil: cold start on mobile Chrome during a work break beats "download our app first." What broke first 1. Guest voice trial vs abuse "First line without signup" is great UX and catnip for bots. We gate with Turnstile + daily caps + health checks on transcription. When any dependency is down, the client must fail with a specific reason. Mapping "browser has no SpeechRecognition" to the same bucket as "trial off" poisoned our analytics and made us chase the wrong incident. Lesson: error taxonomy is product infrastructure. If you collapse distinct failures into one string, you will "fix" the wrong thing for a week. 2. Signup friction vs fraud Password minimum length, email confirmation, Turnstile, IP rate limits. Funnel math is brutal: many client-side signup_started events versus few completed accounts. Instrument failure reasons before you simplify auth into oblivion. Also: mobile layout that pushes the CTA below the fold is a silent killer — we shipped a CSS order fix after measuring absurd scroll height on small screens. 3. SEO vs app shell If marketing content only exists after JS hydration, you lose search and LLM citation. We keep substantive copy in SSR HTML for landings, FAQ, and compare pages, and we publish /llms.txt with stable facts (what it is, what it is not, pricing page link, privacy posture). robots.txt allows major AI crawlers on purpose — GEO needs third-party mentions too, but blocking bots while asking to be cited is self-owning. 4. Cost of "just one more minute of voice" Voice is not a free client-side feature. Quotas (daily + rolling 30-day) and plan gates are core product. The free tier teaches the loop; it is not infinite GPT with your brand on top. Day boundary at 21:00 America/Sao_Paulo surprised early testers who thought "midnight" — document clock skew in the UI, not only in the terms. 5. Cannibalized content clusters Shipping many thin intent landings (interview ×3, airport ×2) diluted authority. Consolidation with 301s toward one strong URL per intent is boring editorial work and better SEO than another 400-word page. Architecture notes worth copying Server owns money and affiliates. Anything that can pay or payout stays off the anon key. RLS is belt; server role is suspenders. Do not pretend RLS alone is your authz story for billing RPCs. First-party analytics. A simple events pipeline beat installing three pixels we would later regret for LGPD and for debugging. Public health endpoint. Expose whether onboarding trial is actually ready (captcha keys + transcription). The marketing site should not promise a mic demo the API cannot honor today. Product decisions I'd repeat Cue the sentence start instead of only scoring the utterance after silence. Say "AI can err" in the FAQ in plain Portuguese. Keep audio off durable storage unless you have a clear teaching reason and a retention policy you can defend. Product decisions I'd rethink earlier Onboarding voice failure instrumentation on day one. A single compare hub plus deep "vs" pages beat a swamp of near-duplicate landings. Brand collisions: "BataTalk" alone is a messy query internationally; plan content around qualified phrases early. Try it / roast it Site: https://batatalk.com.br — first spoken line can work without an account. If you're building similar voice UX, I'm especially interested in how you handle guest quotas without training a fraud dataset on your credit card, and how you explain mic permission inside in-app browsers (Instagram/TikTok WebViews are hostile). Comments welcome — especially dissenting UX takes from people who teach English for a living. Appendix: local clock and support copy Support tickets often start with "my free conversation disappeared." Nine times out of ten it is the Brasília day boundary, a Turnstile failure inside a WebView, or the user opening the site through Instagram's in-app browser where the mic never appears. Write those three cases into the UI empty-states. Your future self will spend fewer nights re-explaining the same incident. If you ship voice in production, budget for: transcription spend, captcha fails, and the emotional cost of a demo that works on your laptop and dies on a mid-range Android Chrome tab with three other media tabs open.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to