ChinesePod

A Mandarin learning service built on a large lesson library, sold to self-paced learners, corporate teams and schools from one site.

chinesepod.com
ChinesePod: Six level pathways with lesson counts and outcomes

What they came with

The catalogue is the business, and it has been accumulating for two decades. Every lesson carries a dialogue, a vocabulary set, expansion notes and exercises, and nearly all of it sits behind a subscription. One library has to be sold three ways from the same site: a self-serve plan, a corporate programme and a school licence, and each buyer interrogates a lesson differently. A lesson page also has to be worth finding from search while the paid material stays paid.

What the build had to do

Six levels carrying the catalogue

The level grid is the primary taxonomy and the primary answer to where do I start. Each of the six levels states its own lesson count and a plain statement of what a learner can do at the end of it, so the block works as navigation and as a placement guide at the same time.

One lesson, five surfaces

A lesson record feeds a video or audio episode, a dialogue, a vocabulary set, expansion notes and exercises, and the home page carousel shows each of those as a real screen from the product. The same record renders five ways, with characters, pinyin and English toggled independently in each.

Two institutional lanes

Companies and schools each get a page with their own outcome language and their own quote form, and the required fields differ: learner count and company website on one, student numbers and institution address on the other. Each lane answers to a second URL as well, and the alias returns the same document byte for byte, so a campaign link can point at either name.

Auth that survives its own history

One client-side component serves signup, login and password recovery, choosing its view from the pathname and recognising the historic URL shapes the site has accumulated, including /accounts/signin, /log-in and /sign-up. It lifts a campaign id from either a signup path segment or a query parameter into a cookie, and it answers an existing-account response inline by offering recovery rather than throwing an error.

One price, three conversations

The pricing page carries a single priced learner plan beside three cards that resolve to a conversation rather than a number: teams, education, and a sample account. Those three have to sit level with a priced card without pretending to be one, which is what keeps the row readable.

Technical detail

Server rendering with no build step

Pages are rendered as HTML on Node and cached at nginx, which reports hits and misses through X-Proxy-Cache. There is no client framework and no bundler. Interactive parts are picked up declaratively instead: carousels and players are found by class and configured from data attributes, including per-breakpoint slide counts and a JSON player config, so a new section is markup and attributes rather than a component and a rebuild. Media is served from S3 and CloudFront, which keeps the heavy files off the application origin.

A versioned API as the only seam

The marketing site talks to the learning application through /api/v1/ and nothing else. The signup form posts JSON to /api/v1/entrance/signup and includes a confirmationRedirectUrl, which lets the backend aim the confirmation email at a different application build without touching the front end. The same component derives its API base from whether it is mounted at the root or under a path prefix, so one file runs in both positions.

Gating and indexing pulling against each other

Gated routes redirect to /login with a continue parameter and return the reader to the page they wanted. Against that, a generated sitemap index splits into pages, lessons, videos, community, pronunciation and twenty-three dictionary shards, and the public lesson page renders lesson id, level, summary, a vocabulary table with a play control on every row, and dialogue lines in characters, pinyin and English. How much of a paid lesson to render for an anonymous reader is the whole trade in that template, and it is decided per block rather than per page.

The stack

Application

Node.jsREST API at /api/v1nginx 1.18Ubuntu

Front end

Bootstrap 5.3.3Swiper 11Plyr 3.7.8Roboto and Roboto SlabCSS custom propertiesVanilla JS

Media and delivery

Amazon S3CloudFrontnginx proxy cache

Measurement and discovery

Google Analytics 4Google Ads tagSwetrixGenerated sitemap index
Sector
Mandarin language learning
Audience
Learners, companies, schools
Shape
Marketing site, gated library
Platform
Node behind nginx
ChinesePod on a phone
chinesepod.com/lesson/binge-watching
ChinesePod: Public lesson: vocabulary table and three-script dialogue
chinesepod.com
ChinesePod: Lesson anatomy carousel showing the study screens