# Content only renders with JavaScript

> Crawlers and AI agents that don’t run JS see an empty page. Server-render or prerender your content so it’s in the HTML, not built client-side.

_Category: AI readiness · Detector `aeo` · Check `content-requires-js` · Severity: warning_

When the HTML your server sends is an empty shell and all the content is painted by JavaScript in the browser, any crawler or agent that doesn’t execute JS — many AI answer engines, some social scrapers, low-power bots — sees a blank page. Client-only SPAs (a very common AI default with plain Vite/CRA) are invisible to exactly the audiences AEO is about.

## Symptoms

- "View source" shows an empty `<div id="root">` and no real content
- Answer engines and some crawlers can’t read or cite the page
- Link previews (Slack, iMessage) are blank or show only the shell
- VibeCheck reports almost no text in the raw server HTML

## How VibeCheck detects it

The `aeo` detector flags this live in the browser and reports it to the widget's Problems list — and to your coding agent over MCP.

- **Issue string:** `Content only renders with JavaScript`
- **Threshold:** The raw server HTML contains fewer than 200 characters of body text

## Root causes

- A client-only SPA (Vite/CRA) that renders entirely in the browser
- No SSR, SSG, or prerendering step in the build
- Content fetched client-side after an empty initial paint

## The fix

Render meaningful content into the HTML on the server or at build time. Use SSR (render per request), SSG/prerendering (render at build), or at minimum a static, crawlable fallback in the HTML. The goal: the important text is present in view-source, before any JavaScript runs.

### Steps

1. Adopt a framework mode that emits HTML (SSR or static generation)
2. Ensure the primary content is in the server response, not fetched after load
3. Verify with “view source” (or `curl`) that the text is present without JS

_Verify: real content must be in the raw HTML_

```bash
curl -s https://your-site.com/page | grep -o '<h1>.*</h1>'
```

## Framework-specific fixes

### React

A plain Vite/CRA app is client-only. Move to a framework with SSR/SSG (Next.js, Remix/React Router) or add a prerender step (e.g. `vite-plugin-ssg`) so pages ship real HTML.

```bash
# add build-time prerendering to a Vite React app
npm i -D vite-react-ssg
```

### Next.js

Use Server Components (the App Router default) or `generateStaticParams` for static generation — both put content in the HTML. Avoid pushing whole pages behind `"use client"` with client-side data fetching.

```tsx
// Server Component (default): data is fetched and rendered to HTML on the server
export default async function Page() {
  const post = await getPost()
  return <article><h1>{post.title}</h1>{post.body}</article>
}
```

[Next.js docs](https://nextjs.org/docs/app/getting-started/server-and-client-components)

### Vue

Use Nuxt (SSR or `nuxi generate` for static) so pages render to HTML. A plain Vite Vue SPA is client-only and invisible to non-JS crawlers.

```bash
npx nuxi generate   # prerender all routes to static HTML
```

[Vue docs](https://nuxt.com/docs/getting-started/deployment)

### Svelte

SvelteKit server-renders by default; keep pages using load functions and avoid `export const ssr = false`. Use `prerender = true` for fully static pages.

_+page.ts_

```ts
export const prerender = true // emit static HTML at build
```

[Svelte docs](https://svelte.dev/docs/kit/page-options)

### Vanilla JS

Render the HTML on the server (any templating layer) or generate static HTML files at build. Don’t rely on client JS to inject the primary content.

```html
<!-- server-rendered: the content is already in the HTML -->
<main><h1>Real title</h1><p>Real content, present before any JS runs.</p></main>
```

## FAQ

### Doesn’t Google run JavaScript now?

`Googlebot` does render JS, but on a delay and with a budget, and many other crawlers and AI answer engines do not render at all. Server-rendered HTML is read immediately and universally — you shouldn’t rely on client rendering for content you want indexed and cited.

### Is a `noscript` fallback enough?

A meaningful `<noscript>` block is better than nothing, but SSR/SSG is far more robust because it gives every consumer the full content. Treat `noscript` as a stopgap, not the fix.

### How does VibeCheck detect this?

It re-fetches your page’s own URL and measures the text in the raw HTML the server returned. Under ~200 characters of body text means the content is being built client-side.

## Related problems

- [Missing structured data (JSON-LD)](https://vibecheck.wcgw.fun/fix/missing-structured-data.md) — AI readiness
- [No markdown content negotiation](https://vibecheck.wcgw.fun/fix/missing-markdown-negotiation.md) — AI readiness
- [Missing meta description](https://vibecheck.wcgw.fun/fix/missing-meta-description.md) — Search visibility
- [Large JavaScript bundles](https://vibecheck.wcgw.fun/fix/large-javascript-bundles.md) — Performance

---

Fix guide from VibeCheck — https://vibecheck.wcgw.fun/fix/content-requires-javascript. Full site index for LLMs: https://vibecheck.wcgw.fun/llms.txt
