June 30, 2026 · 14 min read

SEO for Vibe-Coded Websites: Why Your Beautiful Site Ranks Nowhere (and How to Fix It)

Vibe-coded sites look great and rank nowhere. Here are the SEO pitfalls AI builders ship by default, how to diagnose them in 2 minutes, and the fixes for Lovable, Bolt, Cursor, Replit, Astro, Next.js, Gatsby and 11ty

You described a site to an AI tool. Ten minutes later you had one. It looked great, it deployed in one click, and you shipped it. Then you waited for the traffic.

It never came.

This is the quiet plot twist of the vibe-coding era. The barrier to building a website collapsed, but the barrier to getting that website found didn't move an inch. Worse, the tools that make building so easy ship a set of default choices that are quietly hostile to search engines. Your site can be flawless to a human visitor and close to invisible to Google, ChatGPT, and Perplexity at the same time.

The good news: almost every problem on this list is a known, fixable default, not a deep architectural flaw. You usually don't need to rebuild. You need to know what to look for. This guide walks through the most common SEO pitfalls in vibe-coded and static-site projects, a two-minute test to see if you have them, and the specific fix for each framework.

First, what "vibe coding" actually ships

When you ask an AI builder (Lovable, Bolt, v0, Replit, Cursor) or reach for a modern framework to "build me a website," the most common output is a client-side rendered (CSR) application — usually React or a plain Vite app.

Here's why that matters. A client-side rendered page sends the browser a near-empty HTML file plus a large JavaScript bundle. The page you see is assembled in your browser after that JavaScript runs. To you, it looks instant. To a crawler that reads the raw HTML first, the page arrives looking like this:

<body>
  <div id="root"></div>
  <script src="/assets/index-a1b2c3.js"></script>
</body>

There is no headline, no body copy, no links — nothing to index. The content only exists after the script executes, and you're betting that every crawler runs that script perfectly. Many don't.

The three rendering modes, ranked by SEO safety:

Rendering modeWhat the crawler gets firstSEO risk
Client-side rendering (CSR)Empty shell, content added later by JSHigh
Server-side rendering (SSR)Full HTML, generated per requestStrong
Static site generation (SSG)Full HTML, pre-built at deployVery strong

Google's crawler does eventually try to render JavaScript in a deferred "second wave," but for new or low-authority sites that second wave is slow and unreliable. And there's a 2026 wrinkle that makes this far more urgent than it was even a year ago.

The part nobody warned you about: AI crawlers don't run your JavaScript

If you care about showing up in ChatGPT, Perplexity, or Google's AI Overviews — and if you're reading an Okara blog, you do — this is the section that matters most.

The crawlers behind the major AI answer engines, GPTBot (OpenAI), ClaudeBot (Anthropic), and PerplexityBot, execute essentially zero JavaScript. They read the raw HTML and move on. A crawl analysis of hundreds of millions of AI-bot fetches found these bots don't wait for a client-side render the way Googlebot eventually does.

So a client-side rendered, vibe-coded site isn't just at risk on Google. It is structurally invisible to the AI engines people increasingly use instead of Google. You can be the best answer to a question on the internet, and if your content only appears after JavaScript runs, no AI will ever quote you. AI search optimization (sometimes called GEO, for Generative Engine Optimization) starts with one unglamorous prerequisite: your content has to exist in the HTML. Everything else is downstream of that.

The 2-minute diagnostic: see what crawlers actually see

Before changing anything, find out whether you have a rendering problem. Three checks, fastest first.

1. View the page source. On any content page, press Cmd+U (Mac) or Ctrl+U (Windows). This shows the raw HTML the server sent, before JavaScript runs.

  • If you can find your actual headings and body text in there → you're in good shape.
  • If you see only <div id="root"></div> or a similar empty container → you have a rendering problem that blocks both Google ranking and AI citation.

2. Disable JavaScript and reload. Open DevTools → Command Palette → "Disable JavaScript," then refresh.

  • Page still shows content → good.
  • Page goes blank → that's exactly what a fresh crawler sees.

3. Use Google Search Console's URL Inspection. Run "Test Live URL," then look at the rendered HTML and screenshot. If your text, headings, and links aren't there, the page isn't SEO-ready.

For a thorough audit, crawl the site twice — once with JavaScript rendering off and once with it on. The difference between the two is your exact list of content that depends on JavaScript to appear.

The most common pitfalls (and how to fix each)

Pitfall 1: Client-side rendering — the empty shell

The symptom: View-source shows no real content. Pages take weeks to get indexed, or never do. Nothing shows up in AI answers.

The fix: Get real HTML into that first response. You almost never need to scrap the site you built.

  • Best long-term option: move to a framework that renders HTML by default. Astro is static-first and ships almost no JavaScript unless you ask for it. Next.js gives you SSG and SSR. Remix and SvelteKit/Nuxt server-render by default.
  • Keep your current app: most modern setups let you bolt on static generation or SSR. For a small Vite/React app, vite-ssg pre-renders pages to HTML at build time.
  • Quickest patch: a prerendering service like Prerender.io serves static HTML snapshots to crawlers. It's the most patchwork option, but it gets real content into that first response when nothing else is practical.

A useful trick: the same AI tool that built the site can usually make this change. Ask it to "render this site as static HTML" or "add server-side rendering," then re-run the view-source test.

Pitfall 2: Duplicate or missing titles and meta descriptions

AI builders love to set the page title and meta description once and reuse them on every route. The result: 40 pages that all say the same thing in search results, which tells Google none of them is distinctly about anything.

The fix: inject unique metadata per page from a layout system or your content data, not by hand.

  • Next.js: use the generateMetadata function (App Router) to produce per-page titles, descriptions, canonicals, and Open Graph tags — all present in the HTML on first request.
  • Astro: pass title/description/canonical/OG props through a shared <BaseHead> component. Better, enforce them at the Content Collection schema layer with Zod so a post literally won't build without a valid title and description.
  • Everyone: keep titles ~15–60 characters and descriptions ~120–160. Make each one specific to the page.
// Astro example: make missing/oversized metadata a build error
import { defineCollection, z } from 'astro:content'

const posts = defineCollection({
  schema: z.object({
    title: z.string().min(15).max(70),
    description: z.string().min(120).max(170),
    canonical: z.string().url().optional(),
    publishDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
  }),
})

export const collections = { posts }

Pitfall 3: Enormous unoptimized images

This is the classic. You ship a hero image straight from Figma or a stock site, and it's a 2 MB PNG. Static-site frameworks famously do no image compression out of the box — a Jekyll or vanilla build will happily serve the original file untouched.

A single 2 MB hero image is the most common cause of a poor Largest Contentful Paint (LCP), one of Google's three Core Web Vitals. Optimizing it typically cuts LCP by 1–2 seconds on its own. The numbers are stark: a 2.5 MB original served as an optimized WebP drops to roughly 350 KB, turning a ~4–5 second load on a 3G connection into ~1–2 seconds. One static-site optimizer recorded LCP falling from 2.33s to 1.29s while above-the-fold bytes dropped from 1,122 KB to 502 KB — over half the weight, gone.

The fix:

  1. Convert images to WebP or AVIF (30–50% smaller than JPEG/PNG at the same quality).
  2. Serve responsive sizes via srcset so phones don't download desktop-sized images.
  3. Always set explicit width and height (or CSS aspect-ratio) to prevent layout shift (CLS).
  4. On your LCP image (usually the hero), set loading="eager" and fetchpriority="high". Do not lazy-load it.
  5. Lazy-load everything below the fold.

You don't have to do this by hand:

  • Next.js: the next/image component does format conversion, resizing, and lazy-loading automatically. Add priority to the hero.
  • Eleventy (11ty): the official Image plugin (built on sharp) generates multiple sizes and formats with correct srcset markup at build time.
  • Any static site: run a post-processor like Jampack over your build output (npx @divriots/jampack ./dist). It compresses assets, converts to AVIF/WebP, generates responsive sizes, and sets dimensions — no framework changes required.

Vibe-coded sites often "navigate" with onClick handlers that swap content via JavaScript. To a crawler, an onClick <div> is not a link — it can't follow it, so it never discovers the pages behind it.

The fix: use real anchor tags with href attributes for anything that's a navigable page. Keep onClick for genuine in-page interactions (toggles, modals), not for moving between URLs. If the destination is a real page, it needs a real <a href="/pricing">.

Pitfall 5: No sitemap, no robots.txt, or the wrong base URL

Two failure modes here. Either these files don't exist, or they exist and point at the wrong place — localhost, a preview deployment, or an undefined URL — which advertises pages Google can't reach.

The fix:

  • Generate sitemap.xml and robots.txt at build time. Next.js supports app/sitemap.ts and app/robots.ts as first-class files; Astro has the @astrojs/sitemap integration.
  • Set your production URL in config. In Astro this is the site value in astro.config.mjs — the sitemap and canonical system depend on it. In Next.js it's metadataBase.
  • In robots.txt, make sure you're not blocking AI crawlers (GPTBot, ChatGPT-User, PerplexityBot, ClaudeBot, Google-Extended) unless you've deliberately decided to. Blocking them means those platforms can't cite you.
// astro.config.mjs — the single most common Astro sitemap bug is a missing/wrong site
import { defineConfig } from 'astro/config'
import sitemap from '@astrojs/sitemap'

export default defineConfig({
  site: 'https://yourdomain.com', // must be your real production URL
  integrations: [sitemap()],
})

Pitfall 6: Missing (or JavaScript-injected) structured data

Schema markup (JSON-LD) helps both Google and AI engines understand what your page is — an article, a product, an FAQ. Vibe-coded sites usually skip it, and when they do add it, they often inject it with JavaScript, which means JS-free AI crawlers never see it.

The fix:

  • Add JSON-LD for the relevant type: Article/BlogPosting for posts, Product for products, FAQPage for FAQ sections, Organization for your brand.
  • Put it in the raw HTML, not injected via client-side JavaScript.
  • Astro-specific gotcha: if you use View Transitions, put JSON-LD in the page <body>, not the <head> — head scripts don't re-run on transitions, so the second page you visit ends up with the previous page's schema.

Pitfall 7: Thin content and weak internal linking

Even a technically perfect site won't rank if there's nothing to rank for. Fast-built sites tend to ship thin, generic pages with little linking between them, so authority can't flow and there's no clear reason to rank any single page.

The fix:

  • Give every important page one clear search intent and enough depth that it's genuinely the best answer — not just present, but useful.
  • Link related pages to each other with descriptive anchor text so crawlers (and readers) can move through your site.
  • Earn a few external mentions. A site with zero references from the rest of the web lacks the trust signals search engines and AI both rely on.

Framework cheat sheet

The same principles, translated per tool:

Framework / toolDefault renderingWhat to watchFirst move
AstroStatic HTML (great)client:load overuse bloats JS; metadata props drift across templatesEnforce metadata in Content Collection schema; downgrade client:loadclient:visible
Next.jsSSR/SSG (good)Missing metadataBase; per-page metadata not setUse generateMetadata, next/image, app/sitemap.ts
GatsbySSG (good)Heavy plugins, stale data, large bundlesKeep gatsby-plugin-image; trim plugins
Eleventy (11ty)Static HTML (great)No image optimization by defaultAdd the official Image plugin (sharp)
JekyllStatic HTML (good)Zero image compression; manual metadataCompress images (Jampack or a build step); template per-page meta
Lovable / Bolt / v0Often CSR React/ViteEmpty HTML shellExport and deploy as SSR/SSG (e.g., Next.js), or prerender
CursorWhatever you tell itWill happily generate CSR React with no SSRAdd .cursor/rules requiring SSR/SSG, semantic HTML, real <a> links

The pattern: static-first frameworks (Astro, 11ty, Gatsby, Jekyll) start you in a strong position and mostly need metadata and image hygiene. CSR-by-default builders (Lovable, Bolt, v0, plain React/Vite) need a rendering fix first before anything else matters.

The pre-publish checklist

Run this before you call a vibe-coded site "done":

  • View source shows real headings, body text, and links (not an empty <div>)
  • Page still shows content with JavaScript disabled
  • Every page has a unique title and meta description
  • Canonical URLs are set and point to production
  • Hero/LCP image is WebP or AVIF, sized right, with fetchpriority="high"
  • All images have explicit width/height; below-fold images lazy-load
  • Navigation uses real <a href> links
  • sitemap.xml and robots.txt exist and use your real domain
  • JSON-LD schema is present in the raw HTML
  • AI crawlers (GPTBot, PerplexityBot, ClaudeBot, Google-Extended) are not blocked in robots.txt
  • Each page targets one clear intent and links to related pages

The bottom line

Vibe coding solved the hard part of making a website. It didn't solve the part where a website has to be readable by the machines that send you visitors — and in 2026, that includes a new class of AI crawlers that won't run a single line of your JavaScript.

Almost everything on this list is a default you can change, not a wall you have to demolish. Run the two-minute view-source test today. If your content is in the HTML, you're most of the way there. If it isn't, you've just found the reason your beautiful site has been ranking nowhere — and now you know how to fix it.


Okara is an AI CMO that handles SEO, AI search visibility (GEO), and technical fixes for solo founders and small teams. Its coding agent automates exactly the kind of technical SEO fixes above — turning "my vibe-coded site doesn't rank" into a list of changes that ship. Try it free.


FAQ

Why doesn't my vibe-coded website rank on Google? Most AI builders default to client-side rendering, which sends crawlers an empty HTML shell and adds the content with JavaScript afterward. New, low-authority sites can't rely on Google's deferred JavaScript rendering, so the content is often missed or indexed slowly. The fix is to serve real HTML in the first response using static generation (SSG) or server-side rendering (SSR).

How do I check if my site has a rendering problem? Open any content page and press Cmd+U (Mac) or Ctrl+U (Windows) to view the source. If your headings and body text appear in the raw HTML, you're fine. If you only see an empty container like <div id="root"></div>, your content depends on JavaScript and crawlers may not see it. You can confirm by disabling JavaScript and reloading — a blank page means trouble.

Do AI search engines like ChatGPT and Perplexity read JavaScript? No. The crawlers behind ChatGPT (GPTBot), Claude (ClaudeBot), and Perplexity (PerplexityBot) execute essentially zero JavaScript. They read raw HTML only. A client-side rendered site is invisible to them, so to be cited in AI answers your content must exist in the HTML before any JavaScript runs.

Do I need to rebuild my site to fix SEO? Usually not. You can move to a static-first or SSR framework (Astro, Next.js, Remix), add a prerendering step like vite-ssg to your existing app, or use a prerendering service such as Prerender.io. The most common fix is a rendering change, not a full rebuild — and the same AI tool that built the site can often make it.

How do I fix slow, oversized images on a static site? Convert images to WebP or AVIF (30–50% smaller), serve responsive sizes with srcset, set explicit width/height, and add fetchpriority="high" to your hero image. Tools that automate this include next/image (Next.js), the Eleventy Image plugin (11ty), and Jampack (any static site, run over your build output).

Which framework is best for SEO if I'm vibe coding? Static-first frameworks like Astro and Eleventy give you the strongest SEO starting point — they output clean HTML and ship almost no JavaScript by default. Next.js is the most popular SSR/SSG choice in the React world. CSR-by-default builders (Lovable, Bolt, v0, plain React) can rank, but only after you add static generation, server rendering, or prerendering.

SEO for Vibe-Coded Websites: Why Your Beautiful Site Ranks Nowhere (and How to Fix It) | Okara Blog