I shipped the first version of my assessment-practice platform too eager and slightly wrong in a bunch of ways. None fatal, all instructive. Here's the honest list.
1. I measured reaction time with Date.now(). Which drifts when the system clock syncs and has coarse resolution — for an app whose whole point is timing, this was embarrassing. Switching to performance.now() and event timestamps was the single biggest correctness fix I made.
2. I built the analytics before I had any data. I lovingly engineered a percentile system for a population of… zero. My first users got confidently told they were "in the 50th percentile of 8 people," which is meaningless. I should have shipped raw feedback first and added ranking once real data existed.
3. My landing page shipped as one bundle. Every animation and demo library loaded on first paint, on a page whose job is to convert visitors in three seconds. Code-splitting and lazy-loading below the fold should have been there from day one, not bolted on after a bad Lighthouse score.
4. I over-modelled the database. I stored every trial in its own richly-indexed row forever, planning for scale I didn't have, and made simple queries slower in the process. Storing detail cheaply and summaries fast would have been simpler and faster.
5. I built features nobody asked for. A whole customisation panel, used by approximately no one, while the actual request in every piece of feedback was "more game types." I mistook building for progress.
The meta-lesson, which I'll relearn again anyway: ship the smallest honest version, put it in front of real users, and let them tell you what to build next. Most of my mistakes were me solving imagined problems before I'd met a single real one.
The v2 of all this is live at CogniPrep, a practice platform for psychometric assessments: https://cogniprep.app
Post 8 — Getting a JavaScript-heavy product site to actually rank (Next.js App Router)
Angle: SEO + Next.js. Highly searchable, practical, evergreen.
An interactive, client-heavy site and good SEO feel like opposites — one wants JavaScript, the other wants crawlable HTML. With the Next.js App Router, you can have both, but there are a few things you have to do on purpose. Here's the checklist that moved my pages from invisible to indexed.
Give every page real metadata. The App Router's generateMetadata lets you produce titles, descriptions, and Open Graph tags per route, on the server, so crawlers see them without running your JS:
export async function generateMetadata({ params }) {
const game = await getGame(params.slug);
return {
title: `${game.name} practice — CogniPrep`,
description: game.summary,
alternates: { canonical: `https://cogniprep.app/games/${game.slug}` },
};
}
Set canonical URLs before duplicates bite you. Query params, trailing slashes, and A/B variants all spawn duplicate URLs that dilute ranking. An explicit canonical per page (above) tells search engines which one is the real one.
Ship sitemap.ts and robots.ts. The App Router generates both from code, so your sitemap stays in sync with your actual routes instead of going stale in a static file. Crawlers find your pages faster and you control what they skip.
Keep the indexable content server-rendered. The interactive demos can be client-only (ssr: false is fine for a canvas game), but the words a search engine needs — headings, descriptions, explanations — must be in the server-rendered HTML. Don't hide your actual content inside a client component that only renders after hydration.
Add JSON-LD structured data. A small <script type="application/ld+json"> block describing the page (as a product, article, or FAQ) helps you earn richer search results. It's plain data injected server-side, nothing fancy.
The mental model that fixed this for me: JavaScript can power the experience, but the content and metadata a crawler needs should exist in the HTML before a single line of your JS runs. Split those two concerns, and a heavy interactive site indexes just fine.
Applied all of this to CogniPrep, a practice platform for game-based assessments: https://cogniprep.app
Top comments (0)