Qwik SEO
How Qwik's resumability puts content in the HTML by default and drives near-zero INP and TBT — plus the QwikCity patterns for meta tags, canonical URLs, JSON-LD, sitemaps, and i18n that make a Qwik site rank.
1 evidence signal on this page
- Related live toolPage Speed Test & Core Web Vitals Checker
Qwik is a JavaScript framework whose core trick is resumability: the server serializes app state, listeners, and the component tree into the HTML, so the browser resumes without hydrating (a ~1 KB Qwikloader script still runs — it's not literally zero JS). For SEO that's a double win on routes that are actually server-rendered — content is in the HTML by default (crawlers see it immediately, no render-wave delay) and INP/TBT trend toward zero because no hydration blocks the main thread on load; confirm both with field data rather than assuming them. QwikCity, the meta-framework, lets you mix SSR and SSG per route, plus routeLoader$() for server-side data and a head export for title/meta/OG/canonical/JSON-LD — all server-rendered. Basic SEO works out of the box; the main discipline is using routeLoader$() for dynamic metadata, not client-side effects. The catch is a smaller ecosystem than React/Vue, and — as of this writing — Qwik is on its v2 line, currently in beta.
TL;DR — Qwik is a JavaScript framework that does something unusual: it sends finished HTML and then runs almost no JavaScript when the page loads. Most frameworks have to “hydrate” — re-run their code in your browser to wake the page up — and Qwik skips that entirely by resuming from data baked into the HTML. For SEO that means crawlers see your full content right away, and pages score well on the speed metrics Google measures.
What Qwik is
Most popular frameworks (React, Vue, Angular) build a lot of the page in your browser. Even when the server sends finished HTML, the framework usually has to hydrate it — download its code and re-run it in your browser to make buttons and menus work. That hydration step is one of the biggest reasons pages feel slow: it ties up the browser right when you’re trying to use the page.
Qwik attacks that problem head-on with an idea called resumability. On the server, Qwik renders the HTML and writes everything the page needs to keep working — its state, which clicks do what, the structure of the page — straight into the HTML. When the page loads in your browser, Qwik doesn’t re-run anything. It resumes from that saved information, so it isn’t re-executing your components — but it isn’t literally zero JavaScript either. A tiny (~1 KB) loader script called the Qwikloader does run on load; it just listens for clicks instead of rebuilding the page. Evidence for this claim Qwik serializes application state into HTML and resumes without eagerly re-executing the application on startup. Scope: Qwik resumability; exact JavaScript transferred depends on the application. Confidence: high · Verified: Qwik: Resumable It only fetches the rest of the code it needs the moment you actually click something.
QwikCity is the full toolkit built on top of Qwik — it’s to Qwik what Next.js is to React. It gives you page routing, server rendering, and a clean place to set your titles and meta tags. If you’re doing SEO on a Qwik site, you’re almost certainly using QwikCity.
Why this is good for SEO
Two reasons:
- Your content is in the HTML. Because Qwik renders everything on the server and never needs to render again in the browser, the text and links Google needs are right there in the page source. Google doesn’t have to do anything tricky to see your content — which is the whole game in JavaScript SEO. That’s true when the route is actually server-rendered (SSR or SSG) — check the raw HTML with view-source on your own deployed pages rather than assuming it from the framework alone.
- Pages are fast and responsive. With very little JavaScript running on load, the page reacts to clicks quickly (good INP) and the browser isn’t blocked. Core Web Vitals reward exactly that, so it’s a real — if usually small — advantage. It’s a structural head start, not a guarantee; confirm it with field data on your own site rather than assuming the number.
What you still have to do
“Works out of the box” doesn’t mean “do nothing.” The big one: set your titles,
descriptions, and other meta tags using QwikCity’s head export on each page,
and for pages with dynamic data (blog posts, product pages) pull that data
server-side with routeLoader$() so the meta tags end up in the HTML — not
added later by JavaScript. Evidence for this claim Qwik City routes can provide page metadata through a head export, and routeLoader$ loads route data on the server. Scope: Qwik City metadata and loaders. Confidence: high · Verified: Qwik City: Head Qwik City: Route loaders Get that right and most of Qwik SEO takes care of
itself.
Want the real mechanics — how resumability works, the exact QwikCity patterns for meta tags, canonical URLs, structured data, and sitemaps, plus the pitfalls? Switch to the Advanced tab.
TL;DR — Qwik’s core innovation is resumability: at SSR time it serializes event listeners (as HTML attributes), the component tree (in HTML comments), and app state (in a
qwik/jsonscript) into the HTML, so the browser resumes instead of hydrating — a ~1 KB Qwikloader script still runs, so it’s not literally zero JS. That’s categorically different from React/Vue/Angular/SolidJS, which all hydrate. On routes that are actually server-rendered, the SEO payoff is twofold: content is in the HTML by default (no render-wave delay for crawlers) and INP/TBT trend toward zero because no hydration blocks the main thread on load — verify both with field data rather than assuming them. QwikCity is the meta-framework — SSR and SSG can be mixed per route, not chosen once for the whole project. Metadata lives in theheadexport (title, meta, OG, canonical, JSON-LD), fed byrouteLoader$()for server-side data. Sitemaps are automatic in SSG (for routes actually built) and a dynamic route in SSR. Qwik is currently on its v2 line (beta) as of this writing. The honest caveat: the ecosystem is newer and smaller than React’s.
Resumability: the core idea (and why SEO cares)
Qwik was built by Miško Hevery — the creator of Angular — at builder.io, and its whole reason to exist is to kill hydration. (As of this writing, Qwik is on its v2 line, currently in beta — check the API surface against your installed version before copying code from any source, including this one.) Hydration is what every mainstream framework does after SSR: it downloads the component code and re-executes it in the browser to attach event handlers and rebuild internal state. As Hevery puts it, “Hydration is when an application is downloaded and executed twice, once as HTML and again as JavaScript.” That second execution is pure overhead — the page looks ready but isn’t interactive, and the main thread is blocked.
Qwik instead serializes three things into the HTML at SSR time:
- Event listeners — encoded as HTML attributes (e.g.
on:click="./chunk.js#symbol"). A tiny inline script (the Qwikloader, ~1 KB) attaches one global listener, reads the encoded chunk/symbol on an event, and downloads only that handler code on demand. - Component tree structure — encoded in HTML comments, so Qwik can rebuild the hierarchy without executing component code.
- Application state — serialized into a
<script type="qwik/json">block, so any component can resume without its parent being present.
When the page loads, Qwik doesn’t re-run your components. Evidence for this claim Qwik resumability restores listeners and application state without rerunning component initialization on page startup. Scope: Qwik resumability model. Confidence: high · Verified: Qwik: Resumable As the docs put it, “Resumability is a way for a framework to recover its state without re-executing the application components on the client.” The result, in the docs’ words: “Qwik apps work instantly without any delay because they don’t need hydration, regardless of their size or complexity.”
This is not Islands Architecture (Astro). Islands still hydrate each island individually. Qwik hydrates nothing — there’s no per-island hydration cost because there’s no hydration at all.
What crawlers actually receive
Because Qwik pre-renders everything server-side and never re-renders on the
client, the HTML Googlebot fetches already contains your complete content —
text, links, headings, the lot — for a route that’s actually rendered on the
server. Rendering is a per-route decision in QwikCity (SSR, SSG, or a mix); a
route left in client-only mode, or content fetched from a client-side effect
instead of routeLoader$(), doesn’t get this benefit just because the project
uses Qwik. Google processes JavaScript in two waves (HTML first, then a deferred
render pass that can lag hours to days). A heavily client-rendered app risks
content only showing up in that second wave. A properly server-rendered Qwik
route sidesteps that risk: there’s no render-queue delay because the content was
never waiting on client JS in the first place. Treat “Qwik pages behave like
static HTML for crawlers” as the architecture’s intent, then confirm it on your
deployed URLs with view-source — the safest posture for
JavaScript SEO is verifying the response, not
trusting the framework label.
Bing works the same way — a two-wave model that prefers server-rendered HTML for efficiency. Qwik’s HTML-first output benefits both engines for the same reason.
Core Web Vitals: a structural advantage
Google uses field data (CrUX) for the Page Experience signal, and INP replaced FID as a Core Web Vital in March 2024. Here’s where Qwik’s architecture pays off directly: with the Qwikloader as the only script running on load — the docs note sites “can boot with about 1kb of JS (regardless of application complexity)” — there’s almost nothing to block the main thread. Total Blocking Time trends toward zero and INP stays low because there’s no hydration pass competing with the user’s first interactions. A bigger Qwik app doesn’t mean a bigger startup bill; the JavaScript is fetched lazily, per interaction, not as one boot bundle.
That’s the difference from the established frameworks. Next.js, Nuxt, and Angular SSR all put content in the HTML too — they’re not bad for SEO — but they carry a hydration cost that scales with the app. Qwik removes that cost by design.
That’s the architectural expectation, not a measured result. It doesn’t guarantee a specific INP, TBT, or CrUX score for your site — third-party scripts, analytics tags, and per-route data-fetching cost can all eat into the advantage. Confirm it with field data (CrUX, Search Console’s Core Web Vitals report) on your own deployed URLs before making a performance claim to a client or stakeholder; see the How to Measure tab for the specific KPIs and cadence.
Meta tags and the document head
In QwikCity, page metadata lives in a head export from each route file. For
static metadata it’s a constant: Evidence for this claim Qwik City route modules can export a DocumentHead value or function for route metadata. Scope: Qwik City route metadata. Confidence: high · Verified: Qwik City: Head
export const head: DocumentHead = {
title: 'Qwik SEO Guide',
meta: [
{ name: 'description', content: 'How Qwik resumability helps SEO.' },
{ property: 'og:title', content: 'Qwik SEO Guide' },
{ property: 'og:description', content: 'Resumability, meta tags, sitemaps.' },
],
links: [
{ rel: 'canonical', href: 'https://example.com/qwik-seo/' },
],
};All of this is rendered server-side into <head>, so it’s in the HTML the
crawler sees. The canonical pattern — links: [{ rel: 'canonical', href: '...' }]
— is easy to miss but is the right place for it.
For dynamic metadata (blog posts, products), the SEO-critical pattern is
routeLoader$(). It runs server-side before the HTML response, and the head
export can be a function that receives the resolved loader value:
export const usePost = routeLoader$(async ({ params }) => {
return await getPost(params.slug); // runs on the server
});
export const head: DocumentHead = ({ resolveValue }) => {
const post = resolveValue(usePost);
return {
title: post.title,
meta: [{ name: 'description', content: post.excerpt }],
links: [{ rel: 'canonical', href: `https://example.com/blog/${post.slug}/` }],
};
};This is QwikCity’s equivalent of getServerSideProps. The discipline that matters
for SEO: fetch metadata data with routeLoader$(), not in a client-side
effect (useSignal()/useResource$() that run in the browser). If you set the
title from a client effect, it won’t be in the first-wave HTML.
Structured data (JSON-LD)
JSON-LD goes in the head export’s scripts array, rendered inline in
<head> server-side and fully crawlable:
export const head: DocumentHead = ({ resolveValue }) => {
const post = resolveValue(usePost);
return {
title: post.title,
scripts: [
{
props: { type: 'application/ld+json' },
script: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
datePublished: post.date,
}),
},
],
};
};Because it’s serialized into the HTML response, there’s no rendering delay before
the markup is available to crawlers. One setup detail that’s easy to miss: the
scripts field only produces output if your project’s router-head component
actually renders head.scripts (typically via dangerouslySetInnerHTML) — it’s
not automatic just because you populated the array. If a starter template’s
router-head.tsx doesn’t already loop over head.scripts, add that before
trusting the JSON-LD shows up in the response.
Sitemaps and robots.txt
- SSG builds: the
sitemapOutFileconfig option auto-generatessitemap.xmlduring the static build — but it only includes routes that were actually built. A route excluded from the static build (dynamic params you didn’t pre-render, a page gated behind a condition) won’t be in the sitemap even though it exists. - SSR sites: there’s no automatic sitemap — add a route at
src/routes/sitemap.xml/index.tswith aRequestHandlerthat builds and returns the XML dynamically. robots.txt: drop it in/public/so it’s served at the root.- Canonical and sitemap URLs both depend on
origin. QwikCity builds absolute URLs from the configuredorigin/base(or a forwarded-header origin in SSR middleware). A wrong or leaked preview/staging origin in that configuration produces wrong canonicals and sitemap entries in production — check the actual output, not just the config file, after any adapter or deployment change.
International SEO
Two mechanisms, used together:
rewriteRoutesin QwikCity config maps localized URL paths to your routes without duplicating components (e.g./it/documentazione/→/docs/). This gives you clean, localized URLs — the foundation for an international URL structure.- Qwik Speak (a separate library) handles content translation.
- hreflang tags go in the
headexport’slinksarray, pointing each locale at its alternates. See international SEO and hreflang for the cross-engine rules.
SSR vs. SSG for SEO
Both put content in the HTML, so both are crawler-safe — and QwikCity lets you mix modes per route, not just once for the whole project. Choose per route by content shape:
- SSG (static adapter) produces pure HTML files, no server needed — ideal for content that doesn’t change per request. Automatic sitemap (for routes that were actually built), cheapest hosting, fastest TTFB from a CDN. The tradeoff is freshness: content only updates on the next build, and a route that fails to build simply isn’t there.
- SSR renders per request — needed for personalized or frequently changing
data. Run it at the edge (Cloudflare, Vercel, Netlify) for low TTFB, and set
Cache-Controlheaders on SSR responses so the CDN can serve repeats without re-rendering. The tradeoff is that a failure at request time (a slow or erroredrouteLoader$()) affects the live response, so add error handling for the data fetch rather than letting it surface as a broken page.
Neither mode is “more SEO-safe” than the other by default — pick per route based on how often the content changes and what happens if the data source is briefly unavailable, then verify the deployed response either way.
What to watch out for
- Not everything serializes. Class instances, promises, and streams can’t be
put into the HTML state. Code that touches them has to run client-only — keep it
out of anything that needs to be in the server HTML (especially metadata). If you
deliberately exclude a value with
noSerialize(), remember it comes back asundefinedafter the app resumes on the client — don’t reach for it on anything the page still needs to read post-resume, including metadata paths. useVisibleTask$()is a real escape hatch, use it sparingly. It runs eagerly on the client after initial render — the one hook that deliberately works against resumability. It doesn’t block rendering or hurt the initial HTML, but it does mean client JS is running that Qwik would otherwise have deferred. Fine for genuinely client-only work (a chart library, a map); wrong choice for anything that should be feeding server-rendered metadata.- Verify meta tags are in the source, not injected by JS. View-source (not just
DevTools’ rendered DOM) should show your title, canonical, and JSON-LD. If they
only appear in the rendered DOM, you set them from a client effect instead of the
headexport /routeLoader$(). routeLoader$()vs.useResource$()/useSignal(). Loaders run on the server and feed thehead; the others can run in the browser. For anything that must be in metadata or first-wave HTML, use the loader.- Smaller ecosystem. Qwik has far fewer CMS connectors and SEO plugins than Next.js, and a smaller community to draw on. The framework is capable; you’ll just write more glue yourself. builder.io’s own site runs on Qwik, so it’s used in production — but go in eyes-open about the maturity gap.
Where this sits
Qwik is one of two frameworks in the JavaScript frameworks story — the other being SolidJS. The key distinction to keep straight: Qwik resumes; SolidJS (and React, Vue, Angular) hydrate. Both still output content-rich HTML via their meta-frameworks, so crawling is fine either way; the resumability angle is what makes Qwik’s INP/TBT profile structurally the best of the group. For the metric side, see Core Web Vitals; for the foundations, the JavaScript SEO hub.
AI summary
A condensed take on the Advanced version:
- Qwik = resumability. At SSR it serializes event listeners (HTML attributes),
the component tree (HTML comments), and state (a
qwik/jsonscript) into the HTML, so the browser resumes instead of hydrating. This is categorically different from React/Vue/Angular/SolidJS, which all hydrate. It’s on its v2 line (currently beta) as of this writing — check the API against your installed version. - Content is in the HTML by default — for routes that are actually server- rendered. SSR/SSG is a per-route decision in QwikCity, not a project-wide guarantee. No client re-render step on those routes → crawlers get complete content with no render-wave delay. Confirm on your own deployed URLs with view-source rather than assuming it from the framework alone.
- CWV is a structural head start, not a guarantee. Sites boot with ~1 KB of JS (the Qwikloader — real JS that does run, not zero) regardless of complexity, so TBT trends toward ~0 and INP stays low when nothing else on the page is blocking the thread. Confirm with field data (CrUX / Search Console) — third-party scripts and route-level data-fetching cost can offset the advantage.
- QwikCity is the meta-framework (Next.js for Qwik): SSR/SSG mixed per route,
file-system routing,
routeLoader$()for server-side data. - Metadata lives in the
headexport — title, meta, OG,canonical(inlinks), and JSON-LD (inscripts) — all server-rendered. For dynamic pages, makeheada function fed byrouteLoader$(); don’t set metadata from client-side effects. Thescriptsfield only outputs if yourrouter-headcomponent actually rendershead.scripts— check that it does before trusting the JSON-LD shows up. - Sitemaps: automatic in SSG (
sitemapOutFile, limited to routes actually built); a dynamicsrc/routes/sitemap.xml/index.tsroute in SSR.robots.txtgoes in/public/. Canonical and sitemap URLs both depend on the configuredorigin— a leaked preview/staging origin breaks both in production. - i18n:
rewriteRoutesfor localized URLs + Qwik Speak for content; hreflang viahead.links. - Watch out: non-serializable data (class instances, promises, streams) is
client-only, and a
noSerialize()-wrapped value comes backundefinedafter resume;useVisibleTask$()runs eagerly on the client and should be used sparingly; verify meta tags are in view-source, not JS-injected; the ecosystem is smaller than React/Next.js.
Official documentation
Primary-source documentation from Qwik and the search engines.
Qwik / QwikCity
- Qwik documentation — Overview — the docs home; the ~1 KB JS and instant-loading claims.
- Resumability — the canonical explanation of the serialization mechanism and why it isn’t hydration.
- QwikCity overview — the meta-framework: routing, SSR/SSG, the “Next.js for Qwik” framing.
- Routing — file-system routing, the
Linkcomponent’s prefetch, and route rewrites. - Pages / DocumentHead — the
headexport,routeLoader$(), dynamic head, and structured data. - Sitemaps —
sitemap.xmlgeneration androbots.txt. - Deployments — adapters, SSR/SSG platforms, and cache headers.
- JavaScript SEO basics — two-wave rendering and lazy-loaded content in the DOM.
- Core Web Vitals — CWV as a ranking signal (INP, LCP, CLS).
Quotes from the source
On-the-record framing from Qwik’s creator and the official docs. Confirm wording against the live pages before treating any quote as final.
Miško Hevery — creator of Qwik, builder.io
- “Hydration is when an application is downloaded and executed twice, once as HTML and again as JavaScript.” — “Resumability vs Hydration,” builder.io. Read the post
- “Resumability is a way for a framework to recover its state without re-executing the application components on the client.” — “Resumability vs Hydration,” builder.io. Read the post
- “Hydration must execute before the app becomes interactive. Yes, the execution may be lazy, but the button will not process events until Hydration executes.” — “Resumability vs Hydration,” builder.io. Read the post
Qwik official docs (qwik.dev)
- “Your sites and apps can boot with about 1kb of JS (regardless of application complexity).” — Qwik Docs, Overview. Source
- “Qwik apps work instantly without any delay because they don’t need hydration, regardless of their size or complexity.” — Qwik Docs, Resumability. Source
- “QwikCity is to Qwik, as Next.js is to React, Nuxt is to Vue, SvelteKit to Svelte, and Analog is to Angular.” — QwikCity Docs, Overview. Source
- “Thanks to Qwik’s resumability and JavaScript streaming, there is no additional cost to the end user from Qwik City. (zero JavaScript).” — QwikCity Docs, Overview. Source
Qwik SEO checklist
A pass to confirm a QwikCity site is set up to rank:
- You’re using QwikCity (the meta-framework) with SSR or SSG — not the bare library in client-only mode.
- View-source shows your real content and
<a href>links in the raw HTML (not an empty shell). - Dynamic page data is fetched with
routeLoader$()(server-side), not a client-side effect. - Title, description, and OG tags are set via the
headexport — and as a function fed byresolveValue()for dynamic pages. - Canonical URLs are in
head.linksas{ rel: 'canonical', href: '...' }. - JSON-LD is in
head.scriptswithtype: 'application/ld+json'. -
sitemap.xmlis configured —sitemapOutFilefor SSG, or asrc/routes/sitemap.xml/index.tsroute for SSR. -
robots.txtis in/public/. - Cache-Control headers are set on SSR responses (edge SSR for low TTFB).
- hreflang tags are in
head.linksif the site is multilingual; localized URLs viarewriteRoutes. - INP / LCP / CLS verified in the field (CrUX / Search Console), not just lab.
- No critical content depends on non-serializable (class instances, promises, streams) client-only code.
The mental models
1. Resume, don’t replay. The single idea behind Qwik. Every other mainstream framework replays the app in the browser (hydration) to make SSR HTML interactive. Qwik resumes from state serialized into the HTML, executing ~zero JS on load. If you internalize one thing, make it this.
2. The HTML is the content. Because there’s no client render step, what the crawler fetches is the finished page. There’s no second wave to wait on, no “did it render?” risk. Treat a Qwik page like static HTML for crawl/index purposes.
3. Server-side data → metadata.
The chain that decides whether your meta tags are crawlable:
routeLoader$() (server) → head export function → HTML <head>. Break the
chain by fetching in a client effect and your title/OG/canonical fall out of the
first-wave HTML.
4. SSG vs. SSR is a hosting/freshness call, not an SEO call. Both put content in the HTML. Pick SSG for static content (free hosting, auto sitemap) and SSR for per-request data (run it at the edge, cache the responses). Crawlability is fine either way.
5. The performance win is INP/TBT, not crawlability. React/Next.js already SSR content for crawlers. What Qwik adds is the absence of the hydration tax — that’s where its CWV advantage lives. Don’t reach for Qwik to “fix crawling React can’t”; reach for it when responsiveness is the lever.
Qwik SEO — cheat sheet
Where each SEO element goes (all in the head export)
| Element | Where | Pattern |
|---|---|---|
| Title | head.title | string (or function for dynamic) |
| Meta description / OG | head.meta | [{ name/property, content }] |
| Canonical | head.links | { rel: 'canonical', href: '...' } |
| hreflang | head.links | { rel: 'alternate', hreflang, href } |
| JSON-LD | head.scripts | { props: { type: 'application/ld+json' }, script } |
| Dynamic data | routeLoader$() | server-side; read via resolveValue() |
SSG vs. SSR for SEO
| SSG (static adapter) | SSR | |
|---|---|---|
| Content in HTML | ✅ | ✅ |
| Sitemap | Auto (sitemapOutFile) | Add a /sitemap.xml route |
| Hosting | Static files / CDN | Server / edge runtime |
| Best for | Stable content | Per-request / personalized |
| Caching | Inherent | Set Cache-Control yourself |
Resumability vs. hydration (the one distinction that matters)
| Hydration (React/Vue/Angular/SolidJS) | Resumability (Qwik) | |
|---|---|---|
| Browser work on load | Re-execute components | Resume from serialized state |
| JS on load | Scales with app | ~1 KB (Qwikloader) |
| TBT / INP | Higher | Near-zero |
| Content in HTML | Yes (with meta-framework) | Yes (always) |
Common mistakes
- ❌ Setting meta tags from a client effect → not in first-wave HTML. Use
head+routeLoader$(). - ❌ Calling Qwik “lazy hydration” → it doesn’t hydrate at all; it resumes.
- ❌ Forgetting an SSR sitemap → SSR has no auto sitemap; add the route.
Check Qwik’s generated HTML
Point BUILD_DIR at the HTML output from the production build:
BUILD_DIR=dist
find "$BUILD_DIR" -name '*.html' -type f | while IFS= read -r file; do
titles=$(grep -Eio '<title>[^<]*</title>' "$file" | wc -l | tr -d ' ')
canonicals=$(grep -Eio '<link[^>]+rel=["'"']canonical["'"'][^>]*>' "$file" | wc -l | tr -d ' ')
descriptions=$(grep -Eio '<meta[^>]+name=["'"']description["'"'][^>]*>' "$file" | wc -l | tr -d ' ')
if [ "$titles" -ne 1 ] || [ "$canonicals" -ne 1 ] || [ "$descriptions" -ne 1 ]; then
printf '%s\ttitle=%s\tcanonical=%s\tdescription=%s\n' "$file" "$titles" "$canonicals" "$descriptions"
fi
doneThis catches routes whose Qwik City head function is missing or duplicated. It does not replace checking field performance and rendered content on deployed URLs.
Patrick's relevant free tools
- SEO Incident Simulator — Practice thirty deterministic technical SEO incident investigations — indexability, crawl controls, redirects, sitemaps, markup, caching, DNS, bot verification, rendering, hreflang, and faceted navigation — with clearly labeled fixture evidence and Find → Fix → Verify handoffs.
- Raw vs. Rendered HTML Checker — See what's in your page's initial HTML versus after JavaScript runs — headless-Chrome rendering only when the page actually needs it, a rendering-strategy verdict (SSR / prerendered / CSR / hybrid), ~15 calibrated JavaScript-SEO checks (noindex, canonicals, robots.txt blocking, links, soft 404s), a side-by-side raw-vs-rendered diff, and shareable reports.
- Page Speed Test & Core Web Vitals Checker — One sentence tells you whether a page passes Core Web Vitals and what to fix first — real Chrome user data (CrUX) with a Lighthouse lab fallback, mobile and desktop side by side, loudly-labeled data sources, and prioritized diagnostic fixes. Plus a bulk origin scorecard with CSV export.
Tools for auditing a Qwik site
- View-source (
view-source:in the browser) — the fastest check: is your content, title, canonical, and JSON-LD actually in the raw HTML, or only in the rendered DOM? - Google Search Console — URL Inspection — confirm the rendered HTML matches
the source: content present, no JS-injected
noindex, canonical resolved. - PageSpeed Insights / CrUX — field Core Web Vitals (INP, LCP, CLS). This is where Qwik’s resumability advantage should actually show up.
- Chrome DevTools (Performance / Lighthouse) — lab TBT and main-thread work; confirm the near-zero startup JS in practice.
- Rich Results Test — validate the JSON-LD you added via
head.scripts. - Site crawlers (Ahrefs Site Audit, Screaming Frog) — crawl the rendered site to check titles, canonicals, hreflang, and internal links at scale.
Mistakes people actually make on Qwik
Resumability removes most of the JS-SEO risk that trips up other frameworks, but these are the real ways teams still shoot themselves in the foot on Qwik.
Setting metadata from a client-side effect
The mistake: pulling a post’s title or description into a useSignal() or
useResource$() that runs in the browser, then trying to set the page title from
that value.
Why it’s wrong: anything computed client-side runs after the server sends its response. It never makes it into the HTML Googlebot fetches on the first pass — it only shows up in the rendered DOM, which is exactly the render-wave delay resumability was supposed to avoid you.
What to do instead: fetch the data with routeLoader$() (server-side) and
make the route’s head export a function fed by resolveValue(), as shown in
the Advanced tab. That’s the only path that lands metadata in the first-wave
HTML.
Running Qwik without QwikCity
The mistake: using the bare Qwik library in client-only mode — no meta- framework, no server rendering — because “resumability doesn’t need hydration so it doesn’t need SSR either.”
Why it’s wrong: resumability describes what happens after the HTML exists. If nothing renders the HTML on the server in the first place, there’s nothing to resume — you’re back to an empty shell for crawlers, no different from any other client-only SPA.
What to do instead: use QwikCity (or another server-rendering adapter) for anything public-facing. It’s what actually produces the server-rendered HTML that gives Qwik its SEO advantage.
Assuming SSR gets an automatic sitemap
The mistake: shipping an SSR Qwik site and expecting sitemap.xml to
exist because it “just works” in the SSG build.
Why it’s wrong: the automatic sitemap is a build-time feature of the
static adapter (sitemapOutFile). SSR sites render per request — there’s no
build step to generate a static XML file from, so nothing produces one unless
you add it.
What to do instead: add a route at src/routes/sitemap.xml/index.ts with
a RequestHandler that builds and returns the XML dynamically, as covered in
the Advanced tab.
Putting non-serializable data in anything the server needs to resume
The mistake: storing a class instance, an open promise, or a stream
reference in state that the head export or a server-rendered component
depends on.
Why it’s wrong: Qwik’s serialization only handles plain, serializable values — anything else can’t be written into the HTML at SSR time, which breaks the exact mechanism (Resumability) that makes Qwik’s SEO story work.
What to do instead: keep non-serializable objects client-only and out of
anything that feeds metadata or server-rendered markup. Convert to plain data
(a resolved value, not the promise or class instance) before it touches
routeLoader$() or head.
Calling Qwik “lazy hydration” in a client pitch or audit note
The mistake: describing Qwik’s behavior as a faster or lazier version of hydration when documenting an audit or briefing a dev team.
Why it’s wrong: it sets the wrong expectation — “lazy hydration” implies the browser eventually re-executes every component, just later. Qwik never re-executes components on load at all; that’s the whole point of resumability, and the distinction changes what you’d even look for when something’s slow.
What to do instead: use “resumes” and “resumability,” matching the docs’ own terminology, and check the Qwikloader’s ~1 KB boot script in DevTools if you need to prove the “no hydration” claim to a skeptical stakeholder.
Standing KPIs for a Qwik site
These are the numbers to track on an ongoing basis, not a one-time launch check — Qwik’s whole value proposition is a performance and crawlability story, so both sides need a metric.
INP (field)
What it tells you: whether real visitors are actually experiencing the low-hydration-cost responsiveness Qwik is supposed to deliver. This is the metric Qwik’s architecture is most directly built to win.
How to pull it: Google Search Console’s Core Web Vitals report, CrUX, or Patrick’s CWV Checker / Core Web Vitals History & Competitor Comparison for a per-URL or per-origin field read.
Benchmark / realistic range: Google’s published threshold is “good” at or under 200ms, “needs improvement” 200–500ms, “poor” above 500ms — the same threshold for every site, not Qwik-specific. A resumable site with a clean Qwikloader boot should sit comfortably in “good,” but a bloated per-interaction chunk or heavy third-party script can still push it up.
Cadence: monthly, or after any release that changes interaction handlers; CrUX data itself is a rolling 28-day window.
TBT (lab)
What it tells you: how much main-thread work is happening on load in a controlled test — this is where you’d catch a regression before it shows up in field INP.
How to pull it: Lighthouse or Chrome DevTools’ Performance panel, run against the live URL (not localhost, where results skew low).
Benchmark / realistic range: there’s no universal “good Qwik” number —
it depends on what’s on the page (third-party scripts, image decode work,
route-level routeLoader$() cost). What you’re checking for is that TBT
stays near-zero relative to a hydrating equivalent of the same page, not
against a fixed target.
Cadence: before/after any deploy that adds a third-party script, analytics tag, or new interactive component.
Indexed page coverage
What it tells you: whether Google is actually crawling and indexing the pages you expect — the practical proof that “content is in the HTML by default” is translating into indexing, not just a theoretical advantage.
How to pull it: Search Console’s Page Indexing report, filtered to the Qwik site’s URL prefix; cross-check against your sitemap’s submitted-URL count.
Benchmark / realistic range: depends entirely on site size and how much of it you intend to have indexed — there’s no fixed target. Track the trend (indexed count moving toward submitted count) rather than chasing a specific number.
Cadence: weekly during and after a migration to Qwik; monthly once stable.
JS transferred on load
What it tells you: whether the “boots with ~1 KB” claim is holding up in practice, or whether third-party scripts and oversized initial chunks have eaten into Qwik’s structural advantage.
How to pull it: the Network panel in Chrome DevTools, filtered to JS, on a cold load with cache disabled.
Benchmark / realistic range: the Qwikloader itself is roughly 1 KB; anything meaningfully larger on initial load is coming from something else on the page (analytics, ads, a polyfill) — investigate rather than compare against a fixed number, since page-to-page variance is expected.
Cadence: after any change to third-party tags, analytics, or shared layout components.
Test yourself: Qwik SEO
Five quick questions on resumability and getting a QwikCity site to rank. Pick an answer for each, then check.
Resources worth your time
My related writing
- JavaScript SEO: A Definitive Guide — rendering, DOM parity, and getting JS-built content indexed; the foundation a Qwik audit builds on.
- Core Web Vitals: A Complete Guide — what INP, LCP, and CLS measure and how they factor into SEO — the metrics Qwik’s architecture optimizes.
- The Beginner’s Guide to Technical SEO — where framework choice and rendering fit in the bigger picture.
My speaking
- How Search Works (SlideShare) — my walkthrough of crawling, rendering, indexing, and ranking — the pipeline a Qwik site has to clear. (My standing disclaimer applies: “This is my understanding of systems… not going to be 100% complete or accurate.”)
From around the industry
- Qwik — Resumability — the clearest primary-source explanation of how serialization replaces hydration.
- QwikCity overview — the meta-framework: routing, SSR/SSG, loaders.
- Qwik — Pages / DocumentHead — the
headexport,routeLoader$(), dynamic head, and structured data. - Qwik — Sitemaps — sitemap.xml generation and robots.txt.
- Builder.io — Resumability vs Hydration — Miško Hevery on why hydration is “executed twice” and how resumability avoids it.
- Google — JavaScript SEO basics — two-wave rendering and what crawlers do with JS.
- r/TechSEO — the community for debugging framework rendering and indexing questions.
Qwik SEO
Qwik SEO is the practice of optimizing sites built with Qwik, a JavaScript framework whose 'resumability' model serializes app state into HTML so the browser never has to hydrate. Content is in the HTML by default, so crawling, indexing, and Core Web Vitals are all structurally strong.
Related: JavaScript SEO, Core Web Vitals
Qwik SEO
Qwik is a JavaScript framework — built by Miško Hevery (creator of Angular) at builder.io — whose headline innovation is resumability. Instead of hydrating (downloading and re-executing the app in the browser to make server-rendered HTML interactive, the way React, Vue, Angular, and SolidJS all do), Qwik serializes the app’s event listeners, component tree, and state directly into the HTML on the server. The browser then resumes from where the server left off without re-executing components — though a ~1 KB “Qwikloader” script still runs on load to listen for events, so it isn’t literally zero JavaScript.
For SEO this matters in two ways, on routes that are actually server-rendered (SSR or SSG — a per-route choice in QwikCity, not a project-wide guarantee). First, the content is in the HTML by default — there’s no client render step, so crawlers see complete content in the first wave with no rendering-queue delay. Second, because there’s no hydration blocking the main thread on load, INP and TBT trend toward near-zero, which is exactly what Core Web Vitals reward — worth confirming with field data rather than assuming it.
QwikCity is Qwik’s meta-framework (what Next.js is to React): file-system routing, SSR and SSG mixable per route, routeLoader$() for server-side data fetching, and a head export for managing title, meta, Open Graph, canonical, and JSON-LD — all server-rendered. Basic SEO works out of the box; the main discipline is using routeLoader$() for dynamic metadata instead of client-side effects. The trade-off is a newer, smaller ecosystem than React or Vue. As of this writing, Qwik is on its v2 line, currently in beta.
Related: JavaScript SEO, Core Web Vitals
Build-time retrieval analysis plus live signals for this exact article. The automatic chunk report includes a deterministic readiness score and is ready without a model download.
Search Console
sampleGA4 traffic (28d)
sampleCloudflare traffic (7d)
sampledCrUX field data (28d, phone)
sampleGoogle NLP entities
localChangelog
Updated Jul 18, 2026.
Editorial summary and recorded change details.Summary
Corrected an unqualified 'zero JavaScript on load' claim (the ~1 KB Qwikloader is real JS that runs), reframed SSR/SSG and 'content is in the HTML' claims as per-route rather than project-wide, added a router-head.tsx rendering caveat to the JSON-LD example (verified live against current qwik.dev docs), noted Qwik's current v2-beta status, and added noSerialize()/useVisibleTask$() failure-mode notes.
Change details
-
Fixed beginner-lens claim of 'roughly zero JavaScript on load' — Qwik still runs a ~1 KB Qwikloader script; content now matches the Advanced lens's accurate description.
-
Reframed 'content is in the HTML by default' and CWV claims across Beginner/Advanced/AI Summary as conditional on a route actually being server-rendered (SSR/SSG is a per-route QwikCity decision), with a pointer to confirm via view-source and field data.
-
Added a caveat to the JSON-LD section: the head export's scripts field only renders if the project's router-head component actually outputs head.scripts (e.g. via dangerouslySetInnerHTML) — verified live against current qwik.dev Pages docs.
-
Added an origin/base caveat to the sitemap section (canonical and sitemap URLs depend on configured origin) and noted SSG sitemaps only include routes actually built.
-
Noted Qwik is currently on its v2 line (beta) as of this writing, and added noSerialize()-returns-undefined and useVisibleTask$()-as-eager-escape-hatch notes to the pitfalls list. The v2-beta status and useVisibleTask$() behaviour were verified live against qwik.dev; the noSerialize() behaviour could not be re-fetched during this pass (the serialization docs URL 404'd) and rests on corroboration from Qwik's serialization guide as cited in next-gen-frameworks.
Full comparison unavailable — no prior snapshot was archived for this revision.