DEV Community

Cristovão F.
Cristovão F.

Posted on

Flutter Web is invisible to Google (and nobody warns you)


I published my portfolio in Flutter Web, sent the link to a few people on LinkedIn, and noticed two strange things:

  1. Sharing the link on WhatsApp generated no preview card — just the raw link.
  2. Searching my own name on Google didn't surface a single line of the site's content.

The cause is the same for both, and it's kind of obvious once you understand it: Flutter Web, on its default renderer (CanvasKit), draws everything on a <canvas>. There's no <h1>, no <p>, no text at all in the DOM. For the visitor's browser, nothing changes — they see the app running smoothly, with all the responsiveness and animation you wrote in Dart. But for anything that only reads HTML — Google's crawler, the bot that generates WhatsApp link previews, a screen reader without semantics support — the page is, literally, a blank screen with a <canvas> tag inside it.

You can confirm this in 10 seconds:

curl -s https://your-site.netlify.app/ | grep -c "your name"
# 0
Enter fullscreen mode Exit fullscreen mode

Zero. It doesn't matter how much text you wrote inside a Text() widget — none of it becomes a character in the HTML the server actually serves.

What actually fixes this

There's no magic switch that makes Flutter render to the DOM instead (there was an HTML renderer, but it's been deprecated and had its own visual-fidelity problems). The realistic path is accepting that the app runs on canvas and complementing index.html with what a crawler needs, without depending on Dart to generate it.

1. A block of real text, hidden visually — not with display: none.

This part matters: display: none and visibility: hidden have historically been associated with keyword stuffing, and search engines tend to discount or ignore that content. The correct technique is the same one used for years for accessibility — the sr-only class:

#seo-fallback {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}
Enter fullscreen mode Exit fullscreen mode

The content stays in the DOM, takes up zero space on screen, and isn't treated as cloaking — as long as it's the same content the page actually has, just in plain text. In my case, it's literally the same text that Flutter's Semantics tree already exposes to screen readers: name, role, summary, experience, skills. It's a genuine win-win — the same block improves both accessibility and indexability.

2. Complete Open Graph and Twitter Card tags, including a fixed-size og:image (1200×630). Without this, every link you send falls flat with no preview — and that's the kind of thing that decides whether someone clicks or scrolls past.

3. JSON-LD with schema.org/Person. It's not text for a human to read — it's a structured block that tells Google "this is a person, their role is X, their site is Y":

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Person",
  "name": "Your Name",
  "jobTitle": "Mobile Software Engineer",
  "url": "https://yoursite.com/",
  "sameAs": ["https://linkedin.com/in/..."]
}
</script>
Enter fullscreen mode Exit fullscreen mode

4. robots.txt and sitemap.xml. Sounds obvious, but on a fresh project scaffolded with flutter create, neither comes by default — and without them you have no way to tell Google to crawl the page for the first time.

The practical result

After these changes, the same curl that used to return zero lines now shows the full content — because it's there, in plain HTML, before any JavaScript runs:

curl -s https://your-site.netlify.app/ | grep -c "your name"
# 1
Enter fullscreen mode Exit fullscreen mode

And the link, when pasted into any app, now comes with an image, title, and description.

None of these four things require touching a single line of Dart. It's all in web/index.html and the static files next to it — which makes sense, because the problem was never the app. It was what exists (or doesn't) before the app even loads.

If you've got a portfolio or product running on Flutter Web, it's worth running that curl right now. There's a good chance the result is zero.

Cristovao Farias

Top comments (0)