The Lovable SEO Reality Check: What I Learned Building dot2.solutions
A first-person account of hitting every SEO wall a Lovable app can hit — and what actually fixes them. From duplicate canonicals to broken OG images, here's the honest audit.

Chris
March 15, 2026 · 14 min read
A first-person account of hitting every SEO wall a Lovable app can hit — and what actually fixes them.
I'm a Top 1% Lovable developer. I've shipped over 1.1 million lines of code on the platform. I genuinely love building with it.
And my own website spent months with 5 pages flagged in Google Search Console as "Duplicate without user-selected canonical."
This article is everything I learned fixing that — and being honest about what I couldn't fix.
First, understand what Lovable actually builds
Lovable generates React + Vite applications. That means every site it produces is a client-side rendered (CSR) single-page application (SPA).
What that means in practice: when any crawler — Google, LinkedIn, ChatGPT, Perplexity — fetches a URL on your site, every single page returns this:
<!DOCTYPE html>
<html>
<head>
<title>Your Site Title</title>
<!-- same meta tags for every route -->
</head>
<body>
<div id="root"></div>
</body>
</html>
The actual page content — your headings, your copy, your canonical tags — only exists after React boots up and JavaScript executes in the browser. For human visitors, this is seamless. For crawlers, it creates real problems.
This is not a criticism of Lovable. It's just the architecture. Understanding it is the prerequisite for everything else in this article.
The three crawler problems this creates
1. Google's two-stage crawl introduces indexing delays
Google handles CSR sites through a two-stage process:
- Stage 1: Crawl the initial HTML (sees the empty shell)
- Stage 2: Return later to render JavaScript and capture actual content
Stage 2 is delayed, deprioritized, and not guaranteed to happen quickly. For a new page, this can mean days or weeks before your content is properly indexed.
2. Social platforms and AI crawlers see nothing
LinkedIn, Twitter/X, Facebook, ChatGPT, Perplexity — none of these execute JavaScript. When someone shares a link to your blog post on LinkedIn, the platform fetches the URL to generate a preview. It gets your empty shell and either shows generic information or nothing.
This matters more than most Lovable builders realise. Your Open Graph tags, carefully set via react-helmet-async, are completely invisible to social crawlers.
3. React-helmet-async canonicals arrive too late
My SEO setup looked correct. I had SEOHead components on every page injecting unique titles, descriptions, and canonical tags via react-helmet-async. Google Search Console still flagged 5 pages as "Duplicate without user-selected canonical."
Why? Because those canonical tags are injected by JavaScript — which means Google's first-pass crawl of every page sees the same HTML shell with the same base index.html title and no page-specific canonical. Google sees 10 pages that all look identical and flags them as duplicates.
What I found broken on my own site
Here's the audit of dot2.solutions, honest and unfiltered:
❌ Relative paths on OG images in index.html
<!-- What I had — broken for social crawlers -->
<meta property="og:image" content="/lovable-uploads/image.png" />
<!-- What it should be -->
<meta property="og:image" content="https://dot2.solutions/lovable-uploads/image.png" />
Social crawlers won't resolve relative paths. Every share of my homepage was generating a broken preview. One line fix, significant impact.
❌ Wrong default ogImage in SEOHead component
My SEOHead component had this default:
ogImage = '/lovable-uploads/meet-fin3.webp'
That's a blog post image — a photo related to Fin 3 — being used as the social preview for every page that didn't explicitly set an image. My pricing page, my localization page, my contact page were all sharing a Fin 3 article image when shared on LinkedIn.
❌ Schema duplication
I had comprehensive JSON-LD schema in index.html — Organization, LocalBusiness, Service types. I also had a StructuredData React component used on various pages. On any page that used the component with the same schema types, Googlebot was seeing duplicate schemas.
The fix: keep Organization and LocalBusiness in the static index.html (they're sitewide identity signals that every crawler needs to see). Use the component only for page-specific schemas — Article on blog posts, Service on service pages, FAQPage where relevant.
❌ No static canonical in index.html
My index.html had a comment saying canonicals were "overridden by SEOHead component per page" — but no static fallback. For the homepage, and for any crawlers that don't execute JS, there was no canonical in the static HTML at all.
Adding <link rel="canonical" href="https://dot2.solutions/" /> directly to index.html is a simple mitigation.
❌ Missing AI bot directives in robots.txt
My robots.txt handled Googlebot and Bingbot but had nothing for AI crawlers. Given that GEO (Generative Engine Optimization) is increasingly relevant, this was an oversight. Add explicit entries:
User-agent: GPTBot
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: Claude-Web
Allow: /
The fixes that actually work within Lovable
These are all implementable without leaving the platform. For step-by-step instructions and ready-to-use prompts, see my SEO Setup Guide for Lovable Apps in the help center.
- Absolute URLs for all OG images — in both
index.htmland as the default fallback in your SEOHead component. - A branded default ogImage — use your actual social card image as the fallback, not a random blog post image.
- og:site_name and og:locale — add these to SEOHead:
<meta property="og:site_name" content="your site name" /> <meta property="og:locale" content="en_US" /> - Static canonical in index.html — at minimum for the homepage.
- AI crawler directives in robots.txt — explicit Allow for GPTBot, PerplexityBot, Claude-Web.
- Schema scope separation — sitewide schemas static in
index.html, page-specific schemas via component only. - Build-time meta tag injection — This is the one I actually shipped. Three files make it work:
scripts/prerender-meta.ts— a custom Vite plugin that runs at build time, copiesdist/index.htmlinto route-specific directories, and injects the correct<title>,<meta name="description">,<link rel="canonical">, and OG/Twitter tags for each routescripts/prerender-routes.ts— the route metadata config, auto-generated (see below)vite.config.ts— updated to include the plugin
At build time, Lovable's servers generate files like dist/intercom-expert/index.html and dist/blog/ai-agents/index.html — each with unique meta tags baked into static HTML. Googlebot sees the canonical tags immediately, no JavaScript required.
The detail that makes this maintainable: a scripts/generate-prerender-routes.ts script reads all blog post data files, extracts id, title, and excerpt, and auto-regenerates prerender-routes.ts. It runs automatically as a prebuild npm script — so when you add a new blog post, routes stay in sync without any manual config.
It won't fix the empty <div id="root"> body — React still handles that client-side. But it directly targets the "Duplicate without user-selected canonical" Search Console issue.
The fixes that require leaving Lovable
Let's be honest about the ceiling.
What build-time meta injection does NOT fix: Your page body is still an empty <div id="root"> in the static HTML. Google still needs to execute JavaScript to see your actual content. The two-stage crawl delay remains. For a content-heavy site where organic search is the primary growth channel, this is a real limitation.
What it does fix: The "Duplicate without user-selected canonical" Search Console issue — specifically because that error is about Google not seeing canonical tags in static HTML before JS runs. With the plugin in place, every key route gets its own HTML file with the correct canonical baked in. I'm tracking this in Search Console over the next 4–6 weeks to confirm the flagged pages move to indexed.
The Cloudflare Worker + Prerender.io approach: I looked into this. Real-world reports show DNS instability after a few hours — you end up reverting to A records which bypasses the whole setup. For a production consultancy site, intermittent downtime is a worse problem than slow indexing.
The Framer migration route: Framer solves the SSR problem elegantly for pure marketing sites. But if your Lovable app has Supabase backend, authentication flows, client portals, or any dynamic functionality — Framer can't handle it. You'd end up with two codebases to maintain.
💡 The honest recommendation
For most Lovable builders, the right move is:
- Implement all the fixes above within Lovable
- Move your blog to Ghost if content SEO matters to your business
Ghost handles SSR natively, is purpose-built for content, and your blog is where Google's crawl delay hurts most. Your app stays in Lovable where it belongs.
What Lovable does well for SEO
I don't want this to read as purely critical — there's a lot that Lovable gets right:
- llms.txt support — I implemented
llms.txtandllms-full.txton dot2.solutions. This is genuinely ahead of most sites for GEO and AI crawler visibility. - Clean URL routing — React Router generates clean, descriptive URLs automatically.
- Semantic HTML — Lovable consistently generates proper
<main>,<nav>,<section>structure. - Performance — Vite builds are fast. Core Web Vitals scores tend to be good.
- Flexibility — The SEOHead component pattern, StructuredData component, and
robots.txtconfiguration give you real control within the CSR constraints.
For a consultancy like mine where acquisition comes primarily through LinkedIn, the Intercom community, and referrals — not organic search — the CSR limitations are manageable. For a content-first business where SEO is the primary growth channel, they're more significant.
The checklist
Before you ship a Lovable site, verify each of these (or use the full SEO setup guide for implementation details):
The broader lesson
Building in Lovable is fast. That speed has a tradeoff — you're shipping CSR by default, and CSR has known SEO limitations that no amount of react-helmet-async configuration fully solves.
That's not a reason to avoid Lovable. It's a reason to go in with accurate expectations, implement every mitigation available, and make intentional decisions about where your content actually lives.
My site still ranks. My blog posts get indexed. The Search Console warnings are improving. But I'm also not pretending the architecture is invisible.
Build fast. Ship often. But know what you're building on.
Building with Lovable and need expert guidance?
As a top 1% Lovable developer with 1.1M+ lines shipped, I help teams get production-ready results — from SEO architecture to complex integrations. Whether you need a full build or a code review, let's talk.
See Our Lovable Expert Services →Christopher Boerger is the founder of dot2.solutions, a Swiss AI support automation consultancy specialising in Intercom Fin AI deployment and knowledge base architecture. Top 1% Lovable developer.
Share this article
Building on Lovable? Let's Talk.
As a top-ranked Lovable expert, we help teams ship production-grade web apps — SEO, GEO, and SSR done right from day one.
No commitment required • Free 30-minute consultation • Expert guidance
