I rebuilt my launch platform's SEO in a week. DR went 3 → 30. Here's every problem I found.

Nine SEO problems I found on my own launch platform in one week — an invisible homepage, a schema pointing at a stranger, a blog competing with itself — and what actually moved Domain Rating from 3 to 30.

Lifto is a launch platform where founders show a product working — a 60-second demo video, or a swipeable carousel of up to 5 screenshots. Right now: 279 registered founders, 139 demo reels, 11,819 page views.

A week ago the SEO was quietly broken in about nine different ways. Some of it was my fault, some of it was scaffolding I never looked at again. Here's all of it, including the parts that make me look bad.

---

1. My homepage was invisible to Google

The site is a React SPA. I knew that meant "crawlers might struggle." I did not know it meant my homepage served zero characters of body text — just an empty <div id="root"> and a wall of directory badges in a <noscript> tag.

Google was indexing a blank page with a good title.

Fix: a build step that renders real HTML into the root div for every route. The homepage now ships 4,618 characters of crawlable text; product pages ship the founder's own description and their outbound link. 194 route shells, rebuilt on every deploy.

If you run an SPA, go and curl your own homepage right now. Not DevTools — curl. What Googlebot sees and what your browser renders are different things.

2. I was giving away 104 dofollow backlinks for nothing

Every founder who signed up got a dofollow link to their site from their product page. That's real authority, handed out for creating an account.

Worse: I was advertising the backlink as a reason to display our badge — while already giving it to everyone regardless. The incentive didn't exist.

First fix: outbound links became nofollow by default. A crawler checks whether the founder's site displays the "Featured on Lifto" badge linking back, and only then flips the link to dofollow. Verified automatically, no manual review.

Then I looked at the numbers. Six founders out of 148 had earned it. Four percent.

That's not a bad conversion rate — it's a bad offer. I'd taken something founders valued, put it behind a task most of them would never see, and called it an incentive.

Second fix, and the one that's live now: every approved product gets a dofollow link. Approval is the quality gate — a human looked at it, that's enough. Displaying the badge earns a second dofollow, from a separate partners page.

It has to be a separate page. Search engines count only the first link to a given URL from any one page, so a second link on the product page would have passed exactly nothing — an incentive that was real in my head and worthless in practice. The same mistake as the first one, wearing a different hat.

3. Half my blog was competing with itself

I had an AI job publishing one post per day. 78 posts. I thought that was a content strategy.

I ran a similarity check on the titles. 78 posts collapsed into 37 distinct topics. Forty-one were near-duplicates. Seven pairs had identical titles. One topic — how to get your first customers — had sixteen posts:

How to get your first 1000 customers for your SaaS
How to get your first 10 customers for your SaaS app
How to Get Your First 10 SaaS Customers
How to get your first SaaS customers from scratch

Sixteen pages competing for one query means Google picks one arbitrarily and ranks none of them well. That's keyword cannibalisation — and at that volume of auto-generated near-duplicates it's also the exact pattern Google's scaled-content-abuse policy describes, which is a site-wide penalty, not a per-page one. It would have taken my product pages down with it.

Fix: merged each cluster into one strong article and 301'd the rest to it. 41 redirects, not 41 deletions — those URLs had crawl history worth keeping. Now 37 posts, each about something different.

4. The generator wasn't broken. The topic list was.

Here's the part I found embarrassing. After cleaning up, I looked at why it happened. The generator picked from a list of 25 "topic themes."

Six of those 25 were the same topic phrased differently.

The generator was working perfectly. It was faithfully working through a list I had duplicated myself. There was also a dedupe check that compared themes to published titles by exact string match — but the model writes its own title from the theme, so it never matched, and the check never fired once in 78 posts.

Fix: the list is deduplicated to one entry per topic, the check now compares by keyword overlap instead of exact text, and when every theme is covered the job publishes nothing rather than falling back to random. A missing post costs nothing. A duplicate costs rankings on the post it clones.

Cadence is weekly now, not daily. Volume was never the problem — the topic list was.

5. Structured data existed and was being deleted on every build

My index.html had Organization and WebSite schema in it. Good.

My prerender script stripped every ld+json block out of the template before injecting per-route tags — and only product pages re-added any. So that schema was silently deleted from every prerendered route. The source had it. Production didn't.

Fix: Organization and WebSite are now emitted sitewide, plus FAQPage on the FAQ — parsed out of the FAQ component itself so the markup can't drift from the visible page — and BreadcrumbList on product pages.

6. My schema pointed at a stranger's X account

While fixing that, I noticed sameAs claimed our official X profile was x.com/lifto. That's not mine. twitter:site said @lifto too, which means every card impression from my domain was being attributed to someone else's account.

It was scaffolding from a template I never checked.

Fix: one constant, correct value, referenced by both.

7. Most of my page titles were too long

Product titles were built as name + tagline, with a 70-character tagline budget on top of the name. Some ran to 119 characters. Google cuts around 60, so most of my titles were being truncated mid-sentence in results.

Fix: the tagline now gets whatever budget the name leaves, cut on a word boundary, and is dropped entirely if too little space remains for it to say anything useful. 140 of 222 were over the limit. Now none are.

8. My best page had one internal link

I have a long comparison page — 22 alternatives to Product Hunt. It's the page most likely to rank for people actively looking for what I built.

Search Console showed it with 1 internal link. My blog index had 57.

Every one of my blog posts was about launching a product. Not one of them linked to the page answering exactly that question.

Fix: a contextual "where to go next" block on every post. 1 → 41 pages, one for every article.

9. Small stuff that adds up

  • Five icon-only buttons had no accessible name — a screen reader announced them as "button".
  • Three pages had no <h1> at all in the React component. The feed had one in the prerendered HTML that vanished the moment React hydrated.
  • llms.txt was a list of links. It now describes what Lifto is, how ranking works, and the badge/dofollow exchange — the things you'd want an LLM to say about you when someone asks for Product Hunt alternatives.
  • The sitemap was ordered by updated_at, so any product edit reshuffled every entry and produced a huge diff on every build. Sorted by path now.

---

What actually moved the number

DR went 3 → 30 in six days.

Almost none of that came from the nine fixes above. DR is calculated from inbound links only — it doesn't care about your titles, your schema, or your internal linking. What moved it was directory listings and referring domains.

The nine fixes matter for a different reason: they're what makes traffic convert once it arrives, and what stops a penalty erasing the links you worked for. Fixing the blog duplication didn't raise my DR by a single point. It removed the risk of losing all of it at once.

If you're building something similar: curl your own homepage, count the distinct topics in your blog rather than the posts, and check that the page you most want to rank has more than one internal link pointing at it.

---

Lifto is free to publish on — a 60-second demo or a screenshot carousel, no launch day, no upvote race. liftoapp.com

Topics: seo, domain rating, building in public, technical seo

More from the Lifto blog