Slow English

A weekly bilingual English learning platform that turns real international news into structured B2–C1 lessons.

Slow English screenshot
Role
Designer, Full-stack Developer
Type
Side project, AI-powered web application
Stack
Next.js 15, TypeScript, Drizzle ORM, Neon PostgreSQL, Anthropic Claude API, Clerk, Vercel

What is this?

A weekly bilingual English learning platform that turns real international news into structured B2-C1 lessons. Each week, headlines are fetched from BBC, Guardian, and Al Jazeera, scored for teaching value, and the best one is generated into a full lesson.

Nemo 🐠 asks the questions. Octo 🐙 guides the thinking. Content presents multiple perspectives with Traditional Chinese translations and vocabulary explanations throughout.

How it works

  1. RSS Fetch

    Headlines fetched from BBC, Guardian, Al Jazeera, Yonhap, DW

  2. AI Scoring

    Claude scores each headline for teachability, no web search, plain text only

  3. AI Generation

    Top candidate → Claude generates bilingual B2-C1 lesson with web search

  4. Published

    Article saved to DB and published every Monday at 08:00 Taipei time

Iterations

Several pivots shaped the current product.

Daily → Weekly

Daily generation meant no time to review content before publishing. Switching to weekly added a manual review step and reduced monthly costs by 7x.

Three difficulty levels → Single B2-C1

Generating three versions per article tripled output tokens. As the only user, I always read B2-C1. Simplifying to one level reduced cost and allowed a more focused prompt.

讀新聞學英文 → Slow English

The original design felt like a news feed. As the product shifted to a weekly, topic-based format, the name and visual identity were updated to reflect that.

Nested versions schema → Flat structure

Content was originally stored under a versions JSONB column. With a single level, that wrapper became redundant. Flattening the schema simplified queries and frontend code.

News Learn screenshot
V1: 讀新聞學英文

Challenges & Solutions

API cost optimization

The initial implementation used Claude's web_search tool at the headline scoring stage. Refactored to fetch RSS directly and pass raw headlines as plain text, reserving web search for article generation only. Adding max_uses: 1 to the search tool reduced per-generation cost by a further ~30%.

Duplicate cron runs

Vercel Cron occasionally triggers multiple times on the free tier. Added a guard that checks whether an article already exists for the current week before proceeding.

Removing rss-parser

rss-parser uses the deprecated url.parse() API internally, causing Node.js warnings on every cold start. Replaced with a lightweight custom regex parser using native fetch.

Model migration

Migrating from claude-sonnet-4 to claude-sonnet-4-6 broke tool result handling. The newer model rejected placeholder tool_result content that the older model had silently accepted. Debugged via Vercel logs and updated the tool call flow.

Teachability scoring drift

Early scoring prompts used vague criteria, letting through celebrity gossip and weather reports. Rewrote the prompt with explicit rejection rules and a structured accept/reject framework.

Schema migration

Moving from a nested versions structure to a flat schema required migrating 45 existing articles. Wrote a migration script to extract content, update each article in place, verify the data, then drop the column.

Accessibility

Built with WCAG 2.1 AA standards throughout:

  • Semantic HTML with appropriate landmark regions
  • lang attributes on English (lang="en") and Chinese (lang="zh-TW") content
  • role="status" and aria-live="polite" for loading states
  • Colour contrast audited, all text meets 4.5:1 minimum ratio