
Six Landing Pages, Each Mapped to a Number
A strategy document had existed for a while — keyword research, competitor notes, a list of pages worth building. What it didn't have, in its own words, was an answer to "what should we actually do first." Six candidate pages and no order isn't a plan, it's a menu.
The fix was a single ranked list, sorted by opportunity size × winnability × how much of the work was already done — not "biggest feature first." That last factor mattered more than it sounds: hours earlier the same evening, product work had shipped fair ATS scoring for no-experience resumes, a Campus Recruiter interview persona, and internship-aware job matching. A landing page for that cluster went from "someday" to "today," because the thing it would be advertising had just become true.
Why rank #1 wasn't the biggest feature
The Campus Placement / Fresher page took the top slot, and the plan's own reasoning for it says as much: it wasn't the largest feature, it was the one with the most already in place — including dormant brand equity nobody was using yet, in the form of IIT Bombay and BITS Pilani testimonials that had been sitting in the codebase's Index.tsx file since before there was a page to attach them to.
Checking that this was actually contested ground — rather than assuming it wasn't — meant naming the real competitors: InterviewEra, Greenroom, PlacementDo, PlacementPilot, MockExperts, Eklavvya. A distinct set from the general-market interview-prep tools already documented elsewhere in the strategy, and, on inspection, mostly interview-practice point solutions. None found to combine mock interviews with fair ATS scoring *and* internship-aware job matching in one product — which is the specific claim the new page could make honestly, because the product work backing it had shipped that same evening.
Rank #2 — the AI Mock Interview page — for a plainer reason: the single biggest feature in the product, and it had zero public-facing presence before this.
Rank #3 was the one that looked like it should come later and deliberately didn't: technical SEO fixes (structured data, internal linking) scheduled alongside pages one and two, not after them. The reasoning was compounding, not urgency — every page built after the technical fixes land benefits from them for free; every page built before means going back and redoing it.
Ranks #4 through #6 — Speech Coach, Resume Rewriter, Job Search Agent — filled out over the next two hours, #4 picked specifically as the smallest page with the lowest confirmed competition: the fastest plausible first real ranking, ranked ahead of larger pages precisely because it was easier to win.
A second, narrower plan for a different audience
Somewhere around midnight, a second document showed up: a "GEO" strategy, extending the first one in one specific direction — not what pages or keywords to target, but whether crawlers can see the result at all. GPTBot, ClaudeBot, and PerplexityBot generally don't execute JavaScript the way Googlebot does, and this is a JavaScript-rendered single-page app.
The quick wins shipped that night: an llms.txt file — a curated site summary, per the convention that's emerged for AI assistants — explicit robots.txt allow entries for the named AI crawlers (functionally already permitted by the existing wildcard, but explicit is apparently the current convention, and it removes ambiguity if a stricter policy ever gets added later), sitewide Organization and WebSite structured data placed directly in index.html so it's present without any JavaScript running at all, and an Open Graph image — there hadn't been one, at all, for any shared link.
One item was deliberately left out of that commit: prerendering real per-route meta tags for every page. The stated reason was scope, not priority — it needed a manifest refactor touching every landing and blog page first, and cramming it into the same change would have made the "quick wins" not quick. It shipped as its own thing, 29 minutes later.
The part that actually fixes what a non-JS crawler sees
That prerender pass is the piece with the most engineering under it. All 14 public routes — eight landing pages, the blog index, six posts — were showing the exact same generic homepage title and description to anything that couldn't execute JavaScript, because a client-side-only SPA has nothing else to show before the JS runs.
The fix has two halves. First, a single file — pageMeta.ts — became the one source of truth for every public page's title, description, and structured data, replacing what had been scattered across each page's own component code. FAQ content moved there too, for a specific reason: a page's visible FAQ section and its FAQPage schema had been two separate things that could silently drift apart. Now they're one array read by both.
Second, a build script that runs after vite build, reading the built index.html as a template and writing a real, separate index.html per public route — with that route's actual title, meta, canonical, and structured data baked in as static HTML, not something JavaScript has to construct first. The deploy config needed zero changes to serve it: the existing rule already checks for an exact directory match before falling back to the single-page-app shell, and a prerendered route is exactly that — a directory with an index.html in it.
Real visitors don't see any of this. The prerendered files reference the identical JS bundle, the app hydrates the same way it always did, and the client-side code now reads from the same pageMeta.ts file the build script does — so there's no way for what a browser renders and what a crawler reads to say something different from each other.
What this piece is and isn't
This is what shipped, in the order it shipped, and why that order was chosen. It isn't a claim about what happened to rankings or indexing afterward — that's a separate question, worth its own honest look later if and when there's real evidence for it, not something to bolt onto a build log written the night the code went in.