---
title: "Lovable SEO: the complete 2026 guide | Lovable Field Guide"
url: https://prerenderlovable.com/blog/lovable-seo
description: "Lovable SEO splits across two stacks, and SSR alone does not put your data in the HTML. Test both on your own routes, then close the gaps Lovable leaves to you."
lang: en
---

Skip to content

# Lovable SEO in 2026: what's handled, what isn't

Lovable SEO splits across two stacks, and SSR alone does not put your data in the HTML. Test both on your own routes, then close the gaps Lovable leaves to you.

Published September 11, 2026 Updated September 13, 2026 23 min read

Lovable SEO stopped being one topic on 13 May 2026. That is the day Lovable changed the default stack for new projects from client-rendered React + Vite to TanStack Start with server-side rendering, and almost every “Lovable can’t rank” article written before it is now wrong for half its readers. Which half you are in determines what you need to fix.

So start by checking which stack you’re on.

## The two-part question: which stack, and where does the route fetch its data?

Almost every article on this topic asks only the first half. The second half decides whether a crawler sees your content, and you answer it separately for each route. Run both parts.

### Part one: which stack are you on?

Point `curl` at a live public page and read what comes back **before any JavaScript runs**:

```
curl -s https://yourdomain.com/pricing | head -c 2000
```

| A plain request returns | Your stack | What it means |
| --- | --- | --- |
| Real page copy | TanStack Start, SSR | Everyone gets server-composed HTML, including scanners and AI bots, whether your dynamically loaded content is in that HTML is part two |
| Empty root div | React + Vite, client-rendered | You get the shell; Lovable serves pre-rendered HTML to verified crawlers only |

An “empty root div” means the body of the response is essentially `<div id="root"></div>` plus script tags, with none of your text in it. Use view-source rather than the browser inspector if you prefer clicking: the inspector shows the live DOM after hydration, which is exactly the thing non-rendering crawlers never see.

Two more confirmations worth thirty seconds each. In your project’s dependencies, the presence of `@tanstack/react-start` tells you the upgrade already happened. And in Search Console, URL Inspection shows whether Google has indexed the page. If it hasn’t, check before anything else that you are on a published public URL rather than a preview or a branded workspace URL.

### Part two: where does the route fetch its data?

Run this only once part one told you that you are on TanStack Start. On React + Vite every route returns the shell whatever user agent you send, so the test tells you nothing, and what verified crawlers receive can’t be verified from outside.

Point it at a **page with dynamically loaded content**, meaning anything that isn’t written into the page’s source code: a product list, a blog index, a directory, a search results page. Never the homepage, which is usually hand-written copy and will pass whatever your data layer is doing:

```
curl -s -A 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' \
  https://yourdomain.com/products > /tmp/page.html

# Pick a contiguous phrase that isn't in your source code:
grep -qiF 'Ceramic Pour-Over Kettle' /tmp/page.html \
  && echo 'server-rendered' || echo 'client-fetched: crawlers never see it'
```

Three rules keep this test honest:

- The search phrase must be **one contiguous run of text**. Markup splits phrases across elements, so a product name with a `<span>` in the middle of it will not match even when it is right there on the page.
- If you count words rather than grep for a phrase, strip `<script>` and `<style>` first, or you are measuring inline CSS and serialized JSON instead of copy.
- Identical bytes across two requests mean nothing on their own: cached, prerendered and merely deterministic all look alike. Read `cf-cache-status` and `age` from the response headers before concluding anything about caching.

Do this on three routes and you have the real map: which stack you are on, and which pages actually ship their content to a crawler.

## What the two stacks actually do

The old stack is Vite + React with React Router, client-rendered, deployed as static files on Cloudflare Pages, with server logic living in separately deployed edge functions. The new stack is TanStack Start with TanStack Router, running on Cloudflare Workers, server-rendered by default, with server logic in server functions inside the app. TypeScript, Tailwind and shadcn/ui did not change.

For search, the difference is narrow and decisive:

- **TanStack Start (new default, 13 May 2026; new Enterprise projects from 22 June 2026).** SSR is on by default, so every request is answered with HTML composed on the server, for humans and crawlers alike. What that HTML _contains_ is a separate question. Lovable’s own blog describes the server as one that “runs the React tree, executes the loaders, and streams the result”: loaders being the hinge, and the reason for the section further down this page.
- **React + Vite (everything older).** Lovable applies on-request pre-rendering on deployed public URLs, and that HTML is served **only to verified crawlers**: Google, Bing, social preview bots, and AI engines including ChatGPT, Perplexity, Claude and Gemini. Humans still get the single-page app.

Nobody was force-migrated. Lovable’s own blog is clear that existing projects continue to run as before, and existing projects can be upgraded to TanStack Start through chat or project settings, which costs credits. The upgrade preserves titles, descriptions, analytics scripts, theme, styling, page protection and integration URLs, and it is reversible from version history without affecting your published site. The documented risk is narrow and real: some libraries only work in the browser and can break server rendering in ways the upgrade’s checks miss, so test afterwards.

Do not upgrade purely because you read that SPAs cannot rank. They can. Googlebot renders JavaScript, and it has for years: a Vercel and MERJ study of 100,000+ Googlebot fetches on nextjs.org found that every HTML page, once error and non-indexable responses were excluded, resulted in a full render, with a median render-queue delay of about ten seconds. The tail is longer (roughly three hours at p90, eighteen at p99), but “Google can’t see React” has not been true for a long time. The upgrade helps visitors, scanners and AI bots. Googlebot was already getting pre-rendered HTML. The upgrade doesn’t finish the rendering job, though. It changes where your components render, and your fetches stay wherever they were.

## What Lovable gives you for free

More than most posts on this topic admit. Before you buy or build anything, know what is already there.

**Sitemap and robots.txt.** Both are generated automatically, though the sitemap can pick up URLs that don’t exist (https://encited.com/blog/how-to-generate-sitemap-on-lovable). Lovable validates the sitemap XML, checks robots.txt carries a proper `Sitemap:` directive, and checks URL consistency between the two. The hedge in the docs matters though: they are “not always generated up front.” Verify with `curl -I https://yourdomain.com/sitemap.xml` against the live custom domain rather than assuming. Sitemap and robots.txt on Lovable (https://prerenderlovable.com/blog/lovable-sitemap-and-robots-txt) covers the failure modes, including the one where the SPA catch-all swallows `/sitemap.xml` and returns HTML that Search Console rejects as not-XML.

**Per-route Open Graph.** `og:title`, `og:description` and `og:image` can be set per route, which is the fix for the “every share looks identical” problem described in why Lovable link previews are broken (https://prerenderlovable.com/blog/lovable-link-previews-broken).

**Metadata checks.** The built-in review flags duplicate titles, placeholder text, weak descriptions and wrong canonicals, plus semantic HTML, alt text and indexing status. Findings are graded green (passing), blue lightbulb (low impact), amber (medium) and red X (high).

**Structured-data validation.** Lovable checks your JSON-LD is parseable, checks it matches the visible page content, and flags pages that qualify for rich results but carry no markup. Missing generic `WebSite` or `Organization` markup is deliberately not flagged: broken schema is treated as worse than absent schema, which is the right call.

**Search Console and keyword data.** There is a Google Search Console connector that verifies your property and submits sitemaps from chat. There is also a free Semrush integration for keyword research and competitor analysis with no separate Semrush account, but the docs date that offer as free only **through 15 September 2026**, which is four days from this post’s publication. Check before you plan around it.

**Hosting that won’t throttle a traffic spike.** Lovable states plans do not cap visitors, requests or bandwidth. Note this is about plan caps. Cloud usage still draws from your credit pool either way.

## The limits Lovable documents, and won’t fix for you

These are not gaps you can prompt your way around.

**Only publicly published projects are indexable.** Private projects and branded workspace URLs remain unindexable regardless of SEO setup. If your site lives behind a workspace URL, nothing in this guide will help until you publish it publicly.

**Publishing is a snapshot.** Edits made after you publish do not reach the live site until you republish, and there is no separate staging environment. A surprising share of “Google still shows my old title” reports come down to this. On top of that, pushing commits to GitHub does not update your live site: publishing is a separate action.

**Pre-rendering only exists on deployed public Lovable URLs.** Export the code and deploy it elsewhere and it does not come with you. More on that below, because it is the single biggest own-goal in this whole space.

**Third-party scanners see the shell.** On the old stack, Screaming Frog, Ahrefs, generic “SEO score” tools and most link checkers are not verified crawlers, so they receive the raw SPA and report a contentless page. Reproduce Google’s view with Search Console’s URL Inspection → **View crawled page → HTML** before you believe an audit tool. A Googlebot user agent gets the same shell, and what verified crawlers receive can’t be verified from outside. Meanwhile, the reverse also happens: an in-editor score of 90+ only measures which tags are present in the code. It says nothing about whether the live URL gets indexed.

**The review’s stated scope stops short of ranking.** Lovable’s own documentation says the SEO review does not evaluate content quality, search intent alignment, competitor strength, backlink quality or brand authority, and cannot guarantee rankings. Everything in that sentence is still your job.

## The gaps you own

Here is where the real work sits, roughly in the order that it bites.

### Per-page metadata that survives the first byte

The default failure is one `<title>` and one description baked into `index.html`, served identically for every route. On the old stack, a client-side library like react-helmet does not fix this for non-rendering crawlers, and the mechanism is worth understanding once: helmet mutates `document.head` after the bundle loads and React mounts, which is long after the response body closed. A crawler that never executed your JavaScript cannot observe a DOM mutation. The library works as designed. Nothing that runs in the browser can change a response that has already been sent.

This is the most common gap in Lovable-generated code. In our study of 82 Lovable apps (https://prerenderlovable.com/blog/lovable-tanstack-ssr-study), 63% of route files set no `head:` at all, so no per-page title and no meta description. Encited’s free meta tag audit (https://encited.com/free-tools/meta-tag-audit) shows what a page is actually shipping. Those routes inherit whatever the root route declares, which is how an entire site ends up sharing one title.

Fix it in two layers, and understand that layer one alone is insufficient. Layer one is per-route metadata (TanStack Start’s head API on the new stack, a helmet-style solution on the old one) so Google’s render pass and human visitors get the right tags. Layer two is deleting the competing static title, description, canonical and OG tags from `index.html`, because those are what social bots and non-rendering crawlers actually read.

### Canonicals

Google’s rules here are short: absolute URLs, self-referencing on the canonical page itself, inside `<head>`, never a fragment, and never contradicted by a different signal such as an HTTP `Link` header. The dominant SPA bug is a single `<link rel="canonical" href="https://example.com/">` sitting in `index.html` and shipping with every route, which tells Google to consolidate your entire site into the homepage. That is strictly worse than having no canonical at all.

Two related traps. Google will happily read a JavaScript-injected canonical, but no non-rendering crawler will, so JS injection is the wrong answer for a SPA. And if your site was ever live on a `*.lovable.app` URL, make sure the redirect to your custom domain is a 301 and not a 302: a temporary redirect tells Google to keep the old URL as canonical, which is precisely backwards.

### Navigation a crawler can follow

React makes it very easy to write `onClick={() => navigate('/pricing')}`, which renders a `<button>`. Buttons are not links. One published teardown of a Lovable site counted twenty such instances across eleven public-facing files, including homepage calls to action: invisible internal link structure, in a site that looked perfectly navigable to a human.

Search your codebase for `useNavigate` and `onClick={() => navigate(` and convert anything that moves the user to a new URL into an `<a href>` or a router `<Link>`. Keep buttons for actions. The same section of work covers hash routing: everything after `#` is stripped by the browser and never transmitted to the server, so no origin, CDN, worker or pre-render layer can ever know which hash route was requested. The only fix for `HashRouter` is migrating off it. Nothing at the edge can patch it. Making a Lovable site crawlable (https://prerenderlovable.com/blog/lovable-crawlability) goes through both in detail.

### Soft 404s

A SPA catch-all serves `index.html` with HTTP 200 for `/this-page-does-not-exist`, the router renders a “not found” component client-side, and Google indexes the URL and then flags it as a soft 404. At scale (and hallucinated sitemap URLs create exactly that scale) this burns crawl budget across infinite nonexistent paths. Test it in one line:

```
curl -sI -o /dev/null -w '%{http_code}\n' https://yourdomain.com/this-does-not-exist
# 200 => soft 404.  404 => correct.
```

Google documents two remedies: redirect to a URL that genuinely returns 404, or inject a `noindex` robots meta tag when the record is missing. Note the asymmetry in Google’s own guidance: adding `noindex` with JavaScript works, but _removing_ one with JavaScript may not, because Google may skip rendering entirely once it sees the tag.

### Internal linking and site structure

Crawl discovery is the quiet reason a lot of pages sit in “Discovered – currently not indexed (https://encited.com/blog/fix-discovered-crawled-currently-not-indexed-google-search-console).” A page reachable only from the sitemap has weaker discovery signals than one linked from a navigation menu, a related-content block, or a hub page. Give every page that matters at least one real in-body link from a page that already gets crawled, keep important routes within three clicks of the homepage, and make sure your blog lives in a subfolder on the main domain rather than a subdomain.

### Structured data that reaches the parser

JSON-LD injected in a `useEffect` after an async fetch is the classic failure: Google snapshots the DOM before the data lands, and the markup validates in your browser while capturing empty strings in production. Put JSON-LD in the initial HTML response.

Set expectations honestly while you are there. FAQ and HowTo rich results are gone from Google as of 2026, so schema is no longer a snippet play for those types: `Organization`, `Product`, `Article` and `BreadcrumbList` are where the remaining value is. And a July 2026 investigation reported by Search Engine Land found that ChatGPT strips scripts, iframes and JSON-LD when it converts pages to Markdown for its reading cache. That’s a single agency study that OpenAI hasn’t confirmed, but it should stop anyone promising that schema markup drives ChatGPT citations.

### Sitemaps that describe reality

Lovable generates a sitemap, and AI-generated sitemaps have a documented habit of listing URLs that do not exist while omitting real ones. Hallucinated URLs are not harmless: they generate 404s and soft 404s that dilute trust in the whole file. For any site with a database behind it, generate the sitemap from the actual route manifest plus published content rows rather than prompting for a static list, and watch for the row-level security trap, where an unauthenticated function silently returns zero rows and ships you an empty sitemap.

Hard limits worth knowing: 50,000 URLs or 50MB uncompressed per file, UTF-8, absolute URLs only. Google ignores `<priority>` and `<changefreq>`, so do not emit them. Only set `<lastmod>` to a genuine content-change date: bumping it on every CI deploy trains Google to distrust the field.

## SSR only includes data that a loader fetched

This is the part that catches people who upgraded and assumed they were done. Server-side rendering runs your component tree on the server. It does not run your browser data fetching, and the two are routinely confused.

React’s docs are blunt about the mechanism: effects “only run on the client. They don’t run during server rendering.” A component that loads its data in a `useEffect` therefore renders on the server in its initial state, the loading branch, and that is what ships. TanStack Query says the same thing about queries you have not prefetched: they “wont be server rendered, instead they will be fetched on the client after the application is interactive.” And TanStack Start’s own Query guide states the fix in one sentence: “The route awaits the query for content that must appear in the initial HTML.”

So a page can be 100% server-rendered and contain zero dynamically loaded content. The HTML is a faithful copy of your loading skeleton. “SSR is enabled” and “my data is in the HTML” are independent facts, and only the second one is an SEO fact.

Where the fetch lives is the whole story:

| Where the route gets its data | In the initial HTML? |
| --- | --- |
| Route `loader` | Yes: loaders are isomorphic and their result is serialized into the response |
| `createServerFn` awaited by a loader | Yes |
| `useEffect` + `fetch` inside a component | No: effects do not run during server rendering |
| `useQuery` with no prefetch on the route | No: fetched on the client once the app is interactive |
| Anything behind a click, a tab or a session | No |

Lovable’s docs don’t say which of these rows its generated code ends up in, so we checked. Only 4.4% of page routes in the study load their data in a route loader. Nearly half fetch it inside the component with `useEffect` or `useQuery`, and most of the apps query Supabase tables straight from the browser, including `profiles`, `payments` and `blogs`. Lovable’s own starter code points the same way: its example server function is described as a “Server-side handler invoked from the client”, meaning the browser calls it after the page has loaded.

None of this means Lovable can’t write a loader. The study shows what it writes when nobody asks for one, which is a good reason to ask.

So the honest summary is that Lovable’s TanStack Start apps do server-render their markup, but the generated code rarely fetches data in a loader. Static page chrome lands in the HTML; dynamically loaded content generally does not. The full study (https://prerenderlovable.com/blog/lovable-tanstack-ssr-study) has the method, the per-cohort splits and the strongest single artifact: a public blog post route with no loader, no head, a `useEffect` querying Supabase, and `loading` initialised to `true`, so the server-rendered HTML for every post on that site is a spinner.

Your own routes still need checking. Part two of the test at the top of this page answers it for your site in about fifteen seconds, and the answer can differ from route to route inside one project.

Either way, content that only arrives after hydration is invisible to anything that does not execute JavaScript. Which raises the question of who actually does. Googlebot renders, and so do AI Overviews and AI Mode because they are grounded in Google’s index. Bingbot renders, less reliably, and says so itself: Bing’s standing guidance concedes it is difficult to process JavaScript at scale, and Bing still recommends dynamic rendering: the opposite of Google’s position. Copilot inherits Bing’s view of your site.

Everything else reads raw HTML. Measurement by Vercel and MERJ found that GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, PerplexityBot, Meta-ExternalAgent and Bytespider do not render JavaScript at all: that data is from December 2024 and I could not find a first-party re-measurement, so treat it as the best data available, and it may be out of date. An independent July 2026 investigation of ChatGPT’s retrieval stack, reported by Search Engine Land, reached the same conclusion for ChatGPT specifically. Two methods, nineteen months apart: still vendor-external measurement rather than anything OpenAI or Anthropic has confirmed. Applebot is the documented exception and does render. Link unfurlers are the same story: Slack’s own documentation says its link expander fetches as little of the page as it can using HTTP Range headers, which is architecturally incapable of booting a JavaScript runtime.

So you can rank in Google, appear in AI Overviews, and be completely absent from ChatGPT, Claude and Perplexity, and no Google-based tool in your stack will tell you. AI search visibility for Lovable sites (https://prerenderlovable.com/blog/lovable-ai-search-visibility) picks this up properly.

## The moment you self-host

Exporting to GitHub and deploying on Vercel, Netlify, Cloudflare Pages or your own box is often described as an SEO upgrade. On the old stack it is frequently the opposite: Lovable’s pre-rendering runs on its own deployed public URLs, so the day your DNS points elsewhere, every non-rendering crawler starts receiving an empty shell that it was never receiving before. Nothing in your code changed. Your rendering layer disappeared.

If you are leaving, you need to replace it deliberately. In rough order of robustness:

1. **Build-time static generation.** Prerender every enumerable route to a real HTML file at build time: TanStack Start’s `prerender` option (off by default; turn it on in a build you run on your own machine or CI), React Router’s `prerender()` config, or `vite-react-ssg` on a plain Vite SPA. Data still has to come from a loader for it to land in those files. Everyone gets HTML, there is no runtime dependency, and there is nothing to keep warm. Best fit for marketing sites with a knowable route list.
2. **Framework SSR.** Keep the TanStack Start server and deploy it to a host that runs server code. This is also the migration people mean when they say “just rebuild it in Next.js,” and it is a bigger job than it sounds if your app leans on browser-only libraries.
3. **Edge dynamic rendering.** A worker in front of your origin that detects crawler user agents and serves pre-rendered HTML to them, using a hosted renderer such as Encited, Cloudflare’s Browser Rendering, or your own headless Chromium. Three things make or break this: skip static assets so you do not burn render quota, guard against the renderer fetching your own origin recursively, and fail open so a dead renderer never 5xxs your site. Pre-rendering a Lovable site (https://prerenderlovable.com/blog/lovable-prerendering) has the working middleware and the cache-invalidation details.
4. **A per-route head stamp.** The cheap partial fix: a post-build script that writes per-route `<title>`, description, canonical and OG tags into copies of the built shell. It repairs titles and link previews for every crawler. It does not put body content in the HTML, so be clear with yourself about what it buys.

Two notes on the legality of option three, since it comes up every time. Google defines cloaking as presenting different content to users and search engines _with intent to manipulate rankings_, and explicitly calls dynamic rendering “a workaround and not a recommended solution” because of the complexity involved. Its spam policies don’t mention it. Bing goes further and says a good-faith server-rendered version for bots is acceptable and not cloaking. The content matches as long as snapshots refresh when your pages change, which a managed service like Encited handles for you.

Whichever route you pick, re-run both parts of the test against the new host before you call it done, plus the soft-404 check, plus a `curl -I` on your sitemap and robots.txt at the new domain root. And on Vercel specifically, check the trailing-slash behavior: its default redirect pattern is a documented source of “Page with redirect” reports in Search Console after a migration.

If you take the pre-rendering option, Encited (https://encited.com/) is the one we’d use on a Lovable app: it renders in a headless browser and waits for the network to settle, so data fetched in component hooks ends up in the snapshot, and its crawl logs show which bots received HTML and which pages Google still hasn’t indexed. Lovable’s built-in SEO review doesn’t cover that part at all.

## The diagnostic checklist

Work down it in order. The top items are the ones that most often turn out to be the actual cause.

| # | Check | How to verify | Bad result looks like |
| --- | --- | --- | --- |
| 1 | Project is publicly published | Open the live URL in a private window | Login wall, or a branded workspace URL |
| 2 | Published _after_ your last edit | Republish, then reload | Live site shows old copy |
| 3 | Raw HTML has content | `curl -s URL \| head -c 2000` | Empty root div only |
| 4 | Dynamically loaded content is in the raw HTML | Curl a page with dynamically loaded content, `grep -qiF` a contiguous phrase from your data | Phrase absent, or present only inside a hidden streaming block |
| 5 | Titles differ per route | Curl three routes, grep `<title>` | Identical across all three |
| 6 | Canonical is per-route and absolute | Grep `rel="canonical"` on three routes | Every route claims the homepage |
| 7 | Custom domain redirect is 301 | `curl -I` the old `*.lovable.app` URL | 302 |
| 8 | Sitemap is XML and reachable | `curl -I https://domain/sitemap.xml` | 404, or `content-type: text/html` |
| 9 | Sitemap URLs all resolve | Spot-check ten entries for 200s | 404s from invented URLs |
| 10 | robots.txt allows assets | `curl https://domain/robots.txt` | `Disallow: /assets/` or a blanket block |
| 11 | Missing routes return 404 | `curl -sI -o /dev/null -w '%{http_code}'` | 200 |
| 12 | Navigation uses anchors | Grep for `useNavigate` in nav components | Buttons where links belong |
| 13 | One H1 per page | `$$('h1').map(h => h.textContent)` in console | Three or more |
| 14 | JSON-LD is in the raw response | `curl URL \| grep ld+json` | Present in DevTools only |
| 15 | OG image is absolute and 1200×630 | Grep `og:image` in raw HTML | Relative path, or missing |
| 16 | Google’s stored HTML matches | Search Console → URL Inspection → crawled HTML | Shell, or stale content |

## What to do next

Run part one once, against any live public page. Then run part two against two or three pages with dynamically loaded content (a listing page, a detail page, and anything with a dynamically loaded index) and skip the homepage, which passes regardless of where your data is fetched. Write down two answers, which stack you are on, and for each route whether your content survived into the raw HTML.

If you are on the old stack and staying on Lovable’s hosting, Lovable’s docs say it pre-renders for verified crawlers. That can’t be verified from outside, so watch whether your pages get indexed in Search Console. Then spend the rest of your time on checks 5 through 13, where almost all the remaining damage lives. If you are on the old stack and planning to self-host, decide your rendering replacement _before_ you move the DNS. And if you are already on TanStack Start, the boundary that matters is no longer SSR on or off: it is which fetches sit in a route loader and which run after hydration. Move the ones that carry content you want ranked into loaders, then re-run part two to prove it.

Then open Search Console’s Page Indexing report and read the exclusion reasons rather than the total. “Discovered – currently not indexed” points at internal linking and crawl demand. “Crawled – currently not indexed” points at content. “Soft 404” points at check 11. Each one sends you somewhere different, and guessing between them costs more time than reading them does: the indexing triage guide (https://prerenderlovable.com/blog/lovable-not-indexed-on-google) walks each status through to a cause.

Found something wrong or out of date? Lovable changes fast and we'd rather fix a guide than let it rot.

Disclosure: the team behind this guide also builds Encited, mentioned above.

## Frequently asked questions

Is Lovable SEO friendly in 2026?

Yes, with caveats. Projects created from 13 May 2026 onward use TanStack Start, where server-side rendering is on by default, and older React + Vite projects get on-request pre-rendering that is served only to verified crawlers. But server-side rendering only runs data fetching that lives in a route loader. Content loaded in a route loader is serialized into the HTML; content loaded in a useEffect or an un-prefetched query is fetched after hydration and never appears in the HTML at all. Both stacks also leave per-page metadata, canonicals, internal linking and content quality to you.

How do I tell whether my Lovable project is on TanStack Start or React + Vite?

Run curl against your live URL with a normal browser user agent and check whether the response body contains your page copy or just an empty root div. Real content in a plain request means the TanStack Start SSR stack; an empty root div means the older React + Vite stack, where Lovable's docs say pre-rendered HTML goes to verified crawlers only. What those crawlers receive can't be verified from outside. Whether your dynamically loaded content reaches that HTML is a separate question from which stack you are on, so check it per route rather than per project.

Do I need a pre-rendering service for a Lovable site?

It depends on where each route fetches its data. Lovable's docs say external pre-rendering is unnecessary for Lovable-hosted projects, and that holds for routes that load their data in a route loader. It stops holding for routes that fetch in a component hook, which is the default in most Lovable-generated code, and for any project exported and hosted elsewhere, because Lovable's built-in pre-rendering does not travel with the code. For those routes, a pre-rendering service such as Encited serves crawlers the page after its data has loaded.

Why does my SEO audit tool say my Lovable site is empty when Google indexes it fine?

On the older React + Vite stack, Lovable serves pre-rendered HTML only to verified crawlers such as Googlebot, Bingbot, social preview bots and AI engines. Third-party scanners like Screaming Frog or Ahrefs are not on that list, so they receive the raw single-page app shell and report the page as empty. The audit tool can't see what Googlebot sees.

Can a private Lovable project or a branded workspace URL rank on Google?

No. Lovable's documentation is explicit that only publicly published projects are indexable, and that private projects and branded workspace URLs remain unindexable regardless of any SEO work you do. Publish the project to a public URL before spending time on anything else.

Does adding an llms.txt file help my Lovable site appear in ChatGPT or Perplexity?

There is no evidence it does. Lovable's own documentation says llms.txt is not required and its SEO review does not treat a missing file as a problem, and Google representatives have repeatedly said Google Search does not use it. What AI crawlers actually need is readable text in the raw HTML response: measurement by Vercel and MERJ, published in December 2024, found the major AI crawlers did not execute JavaScript at all.

Read next

## Keep going

- ### What 82 Lovable TanStack apps actually server-render: https://prerenderlovable.com/blog/lovable-tanstack-ssr-study
  Lovable TanStack SSR, measured: in 82 Lovable apps, only 4.4% of pages load their data on the server. The layout reaches the HTML; the data usually doesn't.
- ### Lovable site not showing up on Google: https://prerenderlovable.com/blog/lovable-not-indexed-on-google
  A Lovable app not indexed on Google is usually not a rendering bug, but check anyway. Seven causes ranked by likelihood, with the curl test that proves which one.
- ### Pre-rendering a Lovable app: when you need it: https://prerenderlovable.com/blog/lovable-prerendering
  Lovable prerendering, honestly: SSR puts your page layout in the HTML but usually not your data. Why that happens, and a 30-second test for your own site.
- ### Making a Lovable app crawlable: https://prerenderlovable.com/blog/lovable-crawlability
  Lovable crawlability comes down to links as much as rendering: real anchors, history routing, honest status codes, and a crawl you can run yourself.

## Structured data

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "WebSite",
      "@id": "https://prerenderlovable.com/#website",
      "url": "https://prerenderlovable.com",
      "name": "The Lovable Field Guide",
      "description": "A working journal on Lovable SEO: getting apps built with Lovable indexed, ranked and cited, plus the migration and integration work that comes with it.",
      "publisher": {
        "@id": "https://prerenderlovable.com/#organization"
      },
      "inLanguage": "en"
    },
    {
      "@type": "Organization",
      "@id": "https://prerenderlovable.com/#organization",
      "name": "The Lovable Field Guide",
      "url": "https://prerenderlovable.com"
    },
    {
      "@type": "BlogPosting",
      "@id": "https://prerenderlovable.com/blog/lovable-seo#article",
      "headline": "Lovable SEO: the complete 2026 guide",
      "description": "Lovable SEO splits across two stacks, and SSR alone does not put your data in the HTML. Test both on your own routes, then close the gaps Lovable leaves to you.",
      "datePublished": "2026-09-11T00:00:00.000Z",
      "dateModified": "2026-09-13T00:00:00.000Z",
      "inLanguage": "en",
      "mainEntityOfPage": {
        "@type": "WebPage",
        "@id": "https://prerenderlovable.com/blog/lovable-seo"
      },
      "author": {
        "@type": "Organization",
        "name": "The Lovable Field Guide",
        "url": "https://prerenderlovable.com"
      },
      "publisher": {
        "@id": "https://prerenderlovable.com/#organization"
      },
      "keywords": "lovable seo, is lovable seo friendly, lovable seo optimization, lovable tanstack start seo, lovable prerendering",
      "articleSection": "SEO"
    },
    {
      "@type": "BreadcrumbList",
      "@id": "https://prerenderlovable.com/blog/lovable-seo#breadcrumbs",
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "name": "Home",
          "item": "https://prerenderlovable.com/"
        },
        {
          "@type": "ListItem",
          "position": 2,
          "name": "SEO",
          "item": "https://prerenderlovable.com/seo"
        },
        {
          "@type": "ListItem",
          "position": 3,
          "name": "Lovable SEO: the complete 2026 guide",
          "item": "https://prerenderlovable.com/blog/lovable-seo"
        }
      ]
    },
    {
      "@type": "FAQPage",
      "@id": "https://prerenderlovable.com/blog/lovable-seo#faq",
      "mainEntity": [
        {
          "@type": "Question",
          "name": "Is Lovable SEO friendly in 2026?",
          "acceptedAnswer": {
            "@type": "Answer",
            "text": "Yes, with caveats. Projects created from 13 May 2026 onward use TanStack Start, where server-side rendering is on by default, and older React + Vite projects get on-request pre-rendering that is served only to verified crawlers. But server-side rendering only runs data fetching that lives in a route loader. Content loaded in a route loader is serialized into the HTML; content loaded in a useEffect or an un-prefetched query is fetched after hydration and never appears in the HTML at all. Both stacks also leave per-page metadata, canonicals, internal linking and content quality to you."
          }
        },
        {
          "@type": "Question",
          "name": "How do I tell whether my Lovable project is on TanStack Start or React + Vite?",
          "acceptedAnswer": {
            "@type": "Answer",
            "text": "Run curl against your live URL with a normal browser user agent and check whether the response body contains your page copy or just an empty root div. Real content in a plain request means the TanStack Start SSR stack; an empty root div means the older React + Vite stack, where Lovable's docs say pre-rendered HTML goes to verified crawlers only. What those crawlers receive can't be verified from outside. Whether your dynamically loaded content reaches that HTML is a separate question from which stack you are on, so check it per route rather than per project."
          }
        },
        {
          "@type": "Question",
          "name": "Do I need a pre-rendering service for a Lovable site?",
          "acceptedAnswer": {
            "@type": "Answer",
            "text": "It depends on where each route fetches its data. Lovable's docs say external pre-rendering is unnecessary for Lovable-hosted projects, and that holds for routes that load their data in a route loader. It stops holding for routes that fetch in a component hook, which is the default in most Lovable-generated code, and for any project exported and hosted elsewhere, because Lovable's built-in pre-rendering does not travel with the code. For those routes, a pre-rendering service such as Encited serves crawlers the page after its data has loaded."
          }
        },
        {
          "@type": "Question",
          "name": "Why does my SEO audit tool say my Lovable site is empty when Google indexes it fine?",
          "acceptedAnswer": {
            "@type": "Answer",
            "text": "On the older React + Vite stack, Lovable serves pre-rendered HTML only to verified crawlers such as Googlebot, Bingbot, social preview bots and AI engines. Third-party scanners like Screaming Frog or Ahrefs are not on that list, so they receive the raw single-page app shell and report the page as empty. The audit tool can't see what Googlebot sees."
          }
        },
        {
          "@type": "Question",
          "name": "Can a private Lovable project or a branded workspace URL rank on Google?",
          "acceptedAnswer": {
            "@type": "Answer",
            "text": "No. Lovable's documentation is explicit that only publicly published projects are indexable, and that private projects and branded workspace URLs remain unindexable regardless of any SEO work you do. Publish the project to a public URL before spending time on anything else."
          }
        },
        {
          "@type": "Question",
          "name": "Does adding an llms.txt file help my Lovable site appear in ChatGPT or Perplexity?",
          "acceptedAnswer": {
            "@type": "Answer",
            "text": "There is no evidence it does. Lovable's own documentation says llms.txt is not required and its SEO review does not treat a missing file as a problem, and Google representatives have repeatedly said Google Search does not use it. What AI crawlers actually need is readable text in the raw HTML response: measurement by Vercel and MERJ, published in December 2024, found the major AI crawlers did not execute JavaScript at all."
          }
        }
      ]
    }
  ]
}
```