SolidJS SEO

How to make SolidJS sites crawlable, indexable, and fast — why bare SolidJS is CSR-only, how SolidStart's SSR/SSG fixes it, managing head tags with @solidjs/meta, and why SolidJS hydrates (it is not resumable like Qwik).

First published: Jun 26, 2026 · Last updated: Jul 18, 2026 · Advanced
demand #8 in JavaScript Frameworks#41 in Platform SEO#271 in Technical SEO#378 on the site

SolidJS is a fast, React-like library with fine-grained reactivity and no virtual DOM — but on its own it's client-side rendered, so it ships an empty HTML shell with the same indexing problems as bare React. The fix is SolidStart, the official meta-framework: SSR or SSG put real content and metadata in the first response. Manage head tags with @solidjs/meta (<Title>, <Meta>, <Link>), return real 404s with <HttpStatusCode>, and fetch data on the server with createAsync. One thing not to mix up: SolidJS hydrates — it is not resumable. Resumability is Qwik's trick. SolidJS's win is a Core Web Vitals edge on top of standard SSR.

TL;DR — SolidJS is fine-grained reactive (signals, no virtual DOM) and ships as a CSR library by default — an empty shell with the same Wave-1 indexing problem as bare React. SolidStart is the fix: SSR, SSG (route prerendering), and streaming SSR put content and metadata in the first response. Manage head tags with @solidjs/meta (<Title>, <Meta>, <Link> under a <MetaProvider>), return real status codes with <HttpStatusCode>, and load data on the server with createAsync so it lands in the SSR HTML. The accuracy spine: SolidJS hydrates — it is NOT resumable (that’s Qwik). Its fine-grained reactivity is a client update optimization, not a server-to-client startup one — so the SEO upside is mostly a Core Web Vitals (INP/TBT) edge layered on top of normal SSR.

Evidence for this claim The article's described solidjs-seo capabilities must be evaluated against the platform's current documentation rather than assumed to be search-engine behavior. Scope: Platform-specific capability documentation. Confidence: high · Verified: SolidStart documentation Evidence for this claim Regardless of platform, Google needs crawlable URLs, accessible rendered content, descriptive metadata, and valid search directives. Scope: Google requirements independent of platform. Confidence: high · Verified: Google Search Central: SEO Starter Guide

SolidJS architecture: what’s actually different

SolidJS uses JSX that looks identical to React’s, plus hooks-like primitives (createSignal, createEffect, createMemo). But the execution model is fundamentally different:

  • React re-runs the whole component function when state changes and diffs a virtual DOM to decide what to update.
  • SolidJS compiles JSX to real DOM operations at build time. Components run once; reactive signals wire values directly to the specific DOM nodes that depend on them. When a signal changes, Solid updates only that node — no reconciliation, no virtual DOM overhead.

This is why SolidJS consistently sits near the top of the js-framework-benchmark suite, and why its runtime is dramatically leaner than React’s re-render model. The library itself is roughly 7KB gzipped. Smaller, surgical updates plus a tiny runtime is the performance story — but none of that changes the rendering mode, which is what decides your SEO fate.

CSR by default: the SEO problem

Build with SolidJS alone (no SolidStart) and you get a client-side-rendered app:

  1. The server sends index.html with a near-empty body — typically a <div id="root"></div> and a script tag.
  2. The browser downloads the JS bundle, runs Solid’s reactive code, and populates the DOM.
  3. Googlebot’s first wave sees the empty shell — no content, no metadata.
  4. Googlebot’s second wave (the rendering queue) eventually runs the JS and sees content — but the timing is unpredictable, from hours to weeks.
  5. Bing, social crawlers, and non-JS bots may never see the content at all.

This is the two-wave rendering process, and it’s the same trap any CSR SPA falls into. The practical impacts: slow initial indexing of new content; titles and meta descriptions missing from the raw HTML (so wrong/blank snippets in SERPs); and soft 404s, because a CSR app can’t return a true HTTP 404 — the server always answers 200 with the app shell.

SolidStart: the fix

SolidStart (1.0, stable) is the official SolidJS meta-framework, built on Vinxi (Vite + Nitro). It provides:

  • Isomorphic, file-based routing — files under src/routes/ map to URLs (src/routes/blog/[slug].tsx/blog/:slug). One correction worth being precise about: SolidStart itself does not ship a router or metadata library bundled in — per its own docs, “SolidStart itself does not ship with a Router or Metadata library. Rather, it leaves that open for you to use any library you want.” The routing and @solidjs/meta behavior described here come from adding @solidjs/router and @solidjs/meta explicitly (the official templates add both for you, which is why it can feel automatic — check your package.json rather than assuming they came free with the framework).
  • Multiple rendering modes, chosen per route: CSR, SSR (synchronous, asynchronous, or streaming), and SSG / route prerendering. A SolidStart project isn’t “SSR” or “CSR” as a whole — each route picks its own mode, so verify the rendering config (and the resulting HTML) for the specific route you care about rather than assuming the whole site inherited one setting.
  • Server functions — a "use server" directive for RPC-style server code (data fetching, database access).
  • Adapters for Cloudflare, Vercel, Netlify, Deno, Node, and static hosting.

Version note: this reflects SolidStart 1.0’s current documentation, which labels itself beta and was last updated 2026-04-28 — confirm details against the docs for the version you’re actually running before treating any of this as permanent API surface.

SSR vs. SSG vs. streaming — the SEO read

  • SSR (synchronous, asynchronous, or streaming — SolidStart names these as distinct sub-modes) delivers full content on the first request and is fully indexable on Wave 1. Best for frequently changing pages.
  • SSG (route prerendering) builds HTML at deploy time — the fastest TTFB and LCP, CDN-cacheable, no per-request server work. Best for blogs, docs, and marketing pages. Configure prerendered routes in app.config.ts:
// app.config.ts
import { defineConfig } from "@solidjs/start/config";

export default defineConfig({
  server: {
    prerender: {
      routes: ["/", "/about", "/blog"],
    },
  },
});
  • Streaming SSR sends HTML progressively to improve TTFB on data-heavy pages; Google renders the full streamed output.
  • CSR is fine for authenticated dashboards and tools that don’t need to rank — not for public content.

Managing head tags with @solidjs/meta

The @solidjs/meta package is the SolidJS equivalent of React Helmet or Next.js’s <Head>. It’s SSR-ready and asynchronous. Wrap your app in <MetaProvider> so tags are collected during SSR, then set per-page tags inside your route components:

// src/routes/about.tsx
import { Title, Meta, Link } from "@solidjs/meta";

export default function AboutPage() {
  return (
    <>
      <Title>About Us — My Site</Title>
      <Meta name="description" content="Learn more about us." />
      <Link rel="canonical" href="https://example.com/about" />
      <Meta property="og:title" content="About Us" />
      <Meta property="og:description" content="Learn more about us." />
      <Meta property="og:image" content="https://example.com/og-about.jpg" />
      <main>...</main>
    </>
  );
}

The components available are <Title>, <Meta>, <Link>, <Style>, <Base>, and the <MetaProvider> wrapper. Deduplication is built in: <Meta> tags with matching name attributes override parent definitions — the deepest (most specific) one wins, and this works correctly during SSR. So a root default title and per-page overrides behave as you’d expect.

One warning straight from the docs: don’t add raw <title> tags in your server files — they override @solidjs/meta’s functionality. Always use the <Title> component, never a hand-written HTML title in your server template.

Structured data (JSON-LD)

Google supports JSON-LD whether it’s in the raw HTML or injected by JavaScript, but SSR-rendered JSON-LD is the reliable choice. In SolidStart, render it in the route component:

export default function ArticlePage() {
  const schema = {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": "My Article",
    "author": { "@type": "Person", "name": "Author Name" },
  };

  return (
    <>
      <script
        type="application/ld+json"
        innerHTML={JSON.stringify(schema)}
      />
      <article>...</article>
    </>
  );
}

With SSR enabled, the JSON-LD appears in the server-rendered HTML — the preferred approach for reliability across every crawler. Validate with the Rich Results Test or URL Inspection Tool.

Hydration, not resumability — the accuracy spine

Hydration is the process where, after SSR delivers rendered HTML, the framework attaches to the existing DOM to make it interactive: download the JS bundle, execute the framework code, reconcile state with the existing nodes, attach event listeners. SolidJS uses hydration — the same fundamental mechanism as React’s hydrate, Vue’s createSSRApp, or Angular SSR.

Resumability (Qwik’s architecture) is different: the server serializes the framework’s execution state into the HTML, and the client resumes from there without re-executing components — interactive before any JS runs.

The myth to debunk: SolidJS’s fine-grained reactivity is sometimes confused with resumability because both avoid “re-running” components in the traditional sense. They are not the same thing. Fine-grained reactivity is a client-side update optimization; resumability is a server-to-client startup mechanism. SolidJS still downloads and runs its runtime before the page is interactive.

The SEO implication: both SSR+hydration (SolidJS) and resumability (Qwik) put rendered HTML in the server response, so both are good for indexing. The difference is Time to Interactive and INP on slow devices, where Qwik’s near-zero startup JS has the edge. For crawl and index, SolidStart SSR is already enough.

Core Web Vitals

SolidJS’s runtime advantages can translate into better Core Web Vitals scores on routes that use SSR — but this isn’t automatic or universal. No primary source establishes guaranteed cheap hydration or better outcomes for every Solid/SolidStart route: bundle size, data fetching, third-party scripts, device, and the route’s actual rendering mode all determine the real cost. Treat the following as the mechanism by which SolidJS can help, and measure your own routes to confirm it does:

  • LCP improves because the largest content element is in the server-rendered HTML — no waiting on JS.
  • INP improves because Solid’s fine-grained runtime handles interactions efficiently (no virtual DOM diffing).
  • CLS drops because SSR’d content doesn’t shift as late-loading JS fills in.

A caution worth keeping honest: figures like “SolidJS is ~70% faster than React’s virtual DOM” come from synthetic benchmarks (js-framework-benchmark), not real-world field data. Real CWV depends far more on implementation quality, data fetching, and hosting than on framework choice. Treat the benchmark numbers as directional, and measure your own field data in CrUX / Search Console.

Routing, data fetching, and real 404s

Routing. SolidStart uses the History API by default — never use hash-based (#/page) routing, which prevents reliable URL discovery. Keep trailing-slash behavior consistent and enforce it with server redirects, not just canonicals.

Data fetching for SEO. Content that needs to rank must be loaded on the server so it’s in the SSR HTML. Use createAsync and server functions — not onMount or client-side effects, which run after the HTML is sent:

// Server-side data fetch — content lands in the initial HTML ✓
import { createAsync } from "@solidjs/router";
import { getPost } from "~/lib/api";

export const route = {
  load: ({ params }) => getPost(params.slug),
};

export default function BlogPost(props) {
  const post = createAsync(() => getPost(props.params.slug));
  return <article>{post()?.content}</article>;
}

Real 404s. A catch-all route plus <HttpStatusCode> from @solidjs/start sets the actual HTTP response code on the server — a true 404 instead of a soft 404:

// src/routes/[...404].tsx
import { HttpStatusCode } from "@solidjs/start";

export default function NotFound() {
  return (
    <>
      <HttpStatusCode code={404} />
      <h1>Page Not Found</h1>
    </>
  );
}

Sitemaps, robots.txt, and international SEO

  • robots.txt — add public/robots.txt at the project root and point it at your sitemap. Don’t block the /api/ routes used for XHR data fetching.
  • Sitemap — the third-party solid-start-sitemap plugin (by madaxen86) generates sitemap.xml at build time and supports dynamic routes via parameter mapping or a runtime API route.
  • International SEO — emit hreflang with <Link rel="alternate" hreflang="…"> from @solidjs/meta, and use a subdirectory structure (/en/, /fr/) over subdomains or query parameters.

Common SolidJS SEO pitfalls

  1. Running SolidJS CSR without SolidStart SSR/SSG.
  2. Fetching rankable content in onMount / client-side effects.
  3. Forgetting the <MetaProvider> wrapper (tags won’t render in SSR).
  4. Adding raw <title> HTML tags in server templates (overrides @solidjs/meta).
  5. Using hash-based routing.
  6. Serving soft 404s without <HttpStatusCode>.
  7. Blocking API routes in robots.txt.
  8. Not fingerprinting JS assets (Google caches scripts aggressively).
  9. Content behind a click/accordion/tab that never auto-loads into the DOM.
  10. Assuming SSR works without verifying via View Source / URL Inspection.
  11. Assuming @solidjs/router and @solidjs/meta come bundled with SolidStart — they don’t; SolidStart’s own docs say it “does not ship with a Router or Metadata library.” Official templates add both for you, but a from-scratch or customized setup needs them installed explicitly.
  12. Treating a project as uniformly “SSR” or “CSR” — rendering mode is set per route in SolidStart, so a route you assume is server-rendered may not be.

Where this sits

SolidJS lives in the JavaScript frameworks subcluster alongside Qwik, under the broader JavaScript SEO cluster. For the closest parallel, see React SEO — SolidJS shares React’s JSX and CSR-by-default problem, but fixes it with SolidStart instead of Next.js, and runs faster thanks to no virtual DOM. For how Google sees rendered content, see rendering and crawling.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin an expert quote first.