
Cloudflare Pages SEO Setup: The Complete Guide
Edge delivery, free SSL, and a generous free tier make Cloudflare Pages my default deploy target for content sites. The SEO work is not automatic though; application-level configuration is unavoidable. The checklist below is what I ship on client sites running SvelteKit on Cloudflare Pages.
I am Ignacio (IGNAX), Spain-born and Paraguay-based, a solo full-stack developer and SEO/AEO specialist. The CWV-specific companion is Core Web Vitals on Cloudflare Pages; the publish-time IndexNow article is IndexNow setup.
What is the short checklist?
- Prerender every static route at build time.
- Generate sitemap.xml from the content registry on every build.
- Generate robots.txt with
Sitemap:reference and AI crawlerUser-agentrules. _redirectsfile for apex/www canonical, http→https, and migration redirects._headersfile for HSTS, X-Content-Type-Options, security headers.- Custom domain pointing apex to Cloudflare Pages, with HTTPS forced.
- HSTS preload submission for the apex domain.
- Google Search Console Domain property verified.
- Bing Webmaster Tools property verified.
- IndexNow integration pinging on every publish.
The full setup ships in 2–4 hours on a clean SvelteKit project.

How do I configure SvelteKit for Cloudflare Pages?
The adapter:
pnpm add -D @sveltejs/adapter-cloudflare
Then in svelte.config.js:
import adapter from '@sveltejs/adapter-cloudflare';
export default {
kit: {
adapter: adapter(),
prerender: {
handleHttpError: 'warn',
crawl: true,
},
},
};
And per-route, in +page.ts or +layout.ts:
export const prerender = true;
Prerendering is the single highest-leverage setting. Each prerendered route emits a static HTML file that Cloudflare serves from the edge with sub-50ms TTFB worldwide. Routes that need dynamic data leave prerender = false and run on Workers at the edge.
For background see the SvelteKit adapter-cloudflare documentation.
How do I generate sitemap.xml from the content registry?
The endpoint at src/routes/sitemap.xml/+server.ts:
import { loadAllContent } from '$lib/content/loader';
import { SITE } from '$lib/seo/site';
export const prerender = true;
export async function GET() {
const all = await loadAllContent();
const groups = new Map<string, typeof all>();
for (const c of all) {
const k = `${c.type}:${c.canonicalSlug}`;
if (!groups.has(k)) groups.set(k, []);
groups.get(k)!.push(c);
}
const urls: string[] = [];
for (const group of groups.values()) {
for (const c of group) {
const alts = group
.map(
(s) =>
`<xhtml:link rel="alternate" hreflang="${s.lang}" href="${SITE.url}${urlFor(s)}" />`
)
.join('');
urls.push(
`<url><loc>${SITE.url}${urlFor(c)}</loc><lastmod>${c.updatedAt}</lastmod>${alts}</url>`
);
}
}
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
${urls.join('\n')}
</urlset>`;
return new Response(xml, {
headers: { 'content-type': 'application/xml; charset=utf-8' },
});
}
For the hreflang detail see hreflang in SvelteKit + Paraglide. The sitemap and the page-level <link> tags must agree; both feed from the same loader so drift is impossible.
How do I generate robots.txt?
Endpoint at src/routes/robots.txt/+server.ts:
import { SITE } from '$lib/seo/site';
export const prerender = true;
export async function GET() {
const body = `User-agent: *
Allow: /
User-agent: GPTBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: Google-Extended
Allow: /
Sitemap: ${SITE.url}/sitemap.xml
`;
return new Response(body, {
headers: { 'content-type': 'text/plain; charset=utf-8' },
});
}
The AI crawler entries are explicit allows. The default User-agent: * covers the long tail. Block specific bots by adding explicit Disallow: rules.
How does the _redirects file work?
The _redirects file lives at the root of your build output (static/_redirects in SvelteKit, copied to .svelte-kit/output/). Cloudflare evaluates it before serving any static asset. The syntax:
# Apex/www canonical
https://www.ignax.dev/* https://ignax.dev/:splat 301
# Old URL migrations
/blog/old-post-name /articles/new-post-name 301
# Locale root redirect
/ /en/ 302
Rules to follow:
- 301 for permanent canonical redirects. Apex/www, http/https, old/new URL.
- 302 only for temporary redirects. A/B tests, seasonal promotions.
- Use
:splatfor wildcard captures. The*source maps to:splatdestination. - Test in a fresh browser because Chrome aggressively caches redirects.
The Cloudflare Pages redirects documentation has the full reference.
How do I configure the _headers file?
The _headers file controls HTTP headers per path:
/*
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=(), microphone=()
/sitemap.xml
Content-Type: application/xml; charset=utf-8
Cache-Control: public, max-age=3600
/robots.txt
Content-Type: text/plain; charset=utf-8
Cache-Control: public, max-age=86400
/llms.txt
Content-Type: text/plain; charset=utf-8
Cache-Control: public, max-age=3600
The HSTS header with preload is required for HSTS preload list submission. The max-age=63072000 is two years. The minimum for preload eligibility. Submit to the preload list after the header has been live for 24 hours.
How do I set the canonical URL?
Every page emits a canonical link tag in <head>, pointing to the URL it should be indexed under:
<svelte:head>
<link rel="canonical" href="https://ignax.dev{$page.url.pathname}" />
</svelte:head>
Three rules:
- Self-referential by default. The page's canonical points to itself unless there is a deliberate reason otherwise.
- Absolute URLs:
https://ignax.dev/articles/x, not/articles/x. - No query strings in the canonical unless the query string is semantically meaningful.
For the bilingual case the canonical points to the same-locale URL, and the hreflang tags handle the cross-locale relationship. See multilingual SEO on SvelteKit for the full bilingual setup.
How do I verify in Google Search Console?
Add the site as a Domain property (preferred over URL prefix):
- Search Console → Add Property → Domain → enter
ignax.dev. - Copy the TXT record value.
- In Cloudflare DNS dashboard → add a TXT record on the apex with that value.
- Wait 60 seconds. Verify in Search Console.
Domain properties cover apex, www, all subdomains, and both protocols. They are the right default. After verification, submit https://ignax.dev/sitemap.xml under Sitemaps.
Mirror the setup in Bing Webmaster Tools: same DNS verification, same sitemap submission. Bing's index feeds ChatGPT browsing and Brave Search, both of which feed AI engine citations.
How do I wire IndexNow?
IndexNow lets you ping Bing, Yandex, and aggregators on every publish so they crawl your new URL within minutes rather than days. The integration is one API call per publish:
// src/lib/indexnow.ts
const KEY = process.env.INDEXNOW_KEY!;
const HOST = 'ignax.dev';
export async function pingIndexNow(urls: string[]) {
await fetch('https://api.indexnow.org/indexnow', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
host: HOST,
key: KEY,
keyLocation: `https://${HOST}/${KEY}.txt`,
urlList: urls,
}),
});
}
Place the key verification file at static/${KEY}.txt with the key as its only content. For the full IndexNow walkthrough see IndexNow setup tutorial.
What about Cloudflare Web Analytics?
Cloudflare Web Analytics is the privacy-respecting analytics layer I default to. Enable it in the Cloudflare dashboard for the domain. The beacon is fired by Cloudflare's edge, so no script needs to be added to your HTML.
The data is anonymous, GDPR-friendly, and does not require a cookie consent banner in most jurisdictions. The trade-off versus Google Analytics is fewer dimensions, no marketing-grade segmentation. For most marketing sites the trade is worth it.
How do I monitor uptime?
Three layers:
- Cloudflare's built-in uptime monitoring in the dashboard, free tier.
- A health-check endpoint at
/api/healththat touches the database and external services. Cloudflare pings it every minute, alerts on failure. - Sentry for application errors, integrated via the SvelteKit hook.
The combination catches both infrastructure issues (DNS, SSL, Cloudflare outages) and application issues (database down, API key expired, deployment broke a route).
What external references should I read?
- Cloudflare Pages documentation: canonical reference for Pages-specific config.
- SvelteKit adapter-cloudflare documentation: adapter and Pages-Functions integration.
- Google Search Central, Search Console help: verification and sitemap submission.
- HSTS Preload list submission; the official preload registration.
The Cloudflare Pages + SvelteKit stack is the cheapest, fastest, most SEO-friendly default for content sites in 2026. The work is application-level discipline; Cloudflare handles the infrastructure. For deeper performance work see Core Web Vitals on Cloudflare Pages.
Ready to ship the setup? Email hello@ignax.dev with your stack and timeline. I have the checklist scripted so the deployment is fast.
Frequently asked questions
Does Cloudflare Pages handle SEO automatically?
Partially. Cloudflare gives you HTTPS, edge caching, HTTP/3, Brotli compression, and a free global CDN out of the box, which solves a class of infrastructure-level SEO issues. It does not handle your sitemap, robots.txt, schema, redirects, or canonical URLs, those are application-level concerns you ship as part of your build. The combination is fast, but the application work is unavoidable.
What is the difference between Cloudflare Pages and Cloudflare Workers Sites?
Pages is the modern offering with Git integration, preview deploys, framework adapters (SvelteKit, Next.js, Astro, Remix), and a generous free tier. Workers Sites is the older Wrangler-based static asset deployment. New projects should use Pages exclusively. Workers are still valuable for API routes, edge functions, and server-side logic, Pages and Workers can be combined.
How do I handle redirects on Cloudflare Pages?
Use the _redirects file at the root of your build output. The syntax is the Netlify-compatible format: source, destination, status code, one per line. Cloudflare evaluates _redirects before serving static assets. Use 301 for permanent canonical redirects (apex to www or vice versa, http to https, old URL to new URL) and 302 only for temporary redirects.
Should I host on the apex or on www?
Pick one and redirect the other to it. The choice rarely matters for SEO; consistency does. I default to apex (ignax.dev rather than www.ignax.dev) because it is shorter and easier for users to type. Configure a 301 from www to apex in _redirects, point both DNS records to Cloudflare Pages, and set the apex as the canonical in every page's head.
How do I verify Cloudflare Pages in Google Search Console?
Add the site as a Domain property (preferred) using DNS verification. Cloudflare's DNS dashboard makes adding the TXT record a two-click operation. Domain properties cover apex and all subdomains and protocols. Avoid URL prefix properties unless you specifically need subdomain isolation. After verification, submit your sitemap.xml under Sitemaps.
Can I run dynamic content on Cloudflare Pages with SEO?
Yes via Cloudflare Pages Functions or by pairing Pages with Workers. The trick is making sure dynamic routes still emit SEO-correct HTML server-side. SvelteKit with the Cloudflare adapter handles this cleanly, SSR routes render on Workers at the edge, statically prerendered routes serve from the Pages cache. Both produce indexable HTML.