Static hosts make publishing easy, and that ease hides a few search problems. The same site can answer on a *.netlify.app or *.vercel.app address as well as your own domain. Every pull request can produce a preview URL that looks like a real copy of the site. Redirects live in a config file that nobody reviews, and a mistaken rule can quietly send visitors and crawlers to the wrong place.
This guide is for people who deploy a static site or a static-first framework to Netlify or Vercel and want the technical basics right. You will choose one canonical domain, write permanent redirects with the correct status code, add response headers, keep previews out of search, and test the results. The steps for both hosts are side by side, so you can follow the one you use.
Hosting settings cannot make a page rank. They stop duplicate hostnames, wrong status codes and leaked previews from working against the content you publish.
Before you begin
- Access: you need to edit the site's repository and the host dashboard (domain settings and deploy settings).
- Decide the canonical address, for example
https://www.example.com. If you need help choosing betweenwwwand the bare domain, read www vs non-www. - Know your build output: Netlify reads
_redirectsand_headersfrom the publish directory (the folder deployed after the build). Make sure your static generator copies them there. - Risk: low to moderate. A redirect rule that matches too much can break the whole site. Test on a preview deploy first.
- Back up: commit current config files, and note existing domain settings.
Step-by-step instructions
Step 1: Pick one production domain and redirect the others
Why: if example.com, www.example.com and the host's default *.vercel.app or *.netlify.app address all serve the same pages, search engines see duplicates. Vercel's documentation warns that duplicate URLs do not automatically cause a penalty, but they can split signals and let search engines choose a different canonical than you intended.
Action on Vercel: set a preferred domain in the Domains section of the dashboard, where you can redirect a www subdomain to the apex domain, or set up other domain redirects. To redirect the project's own vercel.app hostname, Vercel's documentation shows a vercel.json rule that matches the host so that preview URLs are not caught:
{
"redirects": [
{
"source": "/:path*",
"has": [{ "type": "host", "value": "your-project\\.vercel\\.app" }],
"destination": "https://www.example.com/:path*",
"permanent": true
}
]
}
Action on Netlify: rules apply to every domain on the site unless you write the full URL in the rule. Netlify's documentation notes that HTTP and HTTPS need separate rules unless you force the redirect. A 301! rule is forced.
http://example.com/* https://www.example.com/:splat 301!
https://example.com/* https://www.example.com/:splat 301!
The source domain must be assigned to the site.
Confirm: curl -I https://your-project.vercel.app/about (or the Netlify equivalent) returns a permanent redirect to https://www.example.com/about, with the path kept.
Step 2: Write moved-page redirects with explicit status codes
Why: the status code tells search engines how long to trust the old address. Google treats 301 and 308 as permanent signals and 302, 303 and 307 as temporary ones. You can compare them in 301 vs 302 redirects.
Action on Netlify: add rules to a _redirects file in the publish directory or to netlify.toml. The default status is 301.
/old-blog/* /blog/:splat 301
/contact-us /contact 301
[[redirects]]
from = "/old-page"
to = "/new-page"
status = 301
Rules are processed in order, so put specific rules before wildcards. _redirects rules run before netlify.toml rules. Use * only at the end of a path segment, and :splat in the destination to carry the rest of the path. Netlify says that for 10,000 or more redirects you should lean on wildcards or placeholders, since a very large rule set can make a deploy fail.
Action on Vercel: use the redirects array in vercel.json. permanent: true gives a 308 and permanent: false gives a 307. The permanent value defaults to true. If you need a 301 or 302 exactly, use statusCode instead (you cannot use both).
{
"redirects": [
{ "source": "/old-blog/:slug", "destination": "/blog/:slug", "permanent": true },
{ "source": "/legacy", "destination": "/new", "statusCode": 301 }
]
}
Point each old URL at its closest replacement rather than sending everything to the home page.
Confirm: each old URL returns the expected status and a single Location hop to a 200 page.
Step 3: Settle trailing slashes and clean URLs once
Why: /about and /about/ can both exist as separate URLs. Pick one, and let the host enforce it.
Netlify: the CDN normalizes URLs before redirect rules run, so your rules match with or without a trailing slash, and a rule cannot add or remove a slash. A rule that tries to remove it can loop forever. The Pretty URLs post-processing setting is on by default and redirects /about and /about.html to /about/.
Vercel: set trailingSlash to false to make /about/ answer with a 308 redirect to /about, or to true for the opposite. cleanUrls: true removes file extensions and answers /about.html with a 308 to /about. Leaving trailingSlash unset means no redirect either way, which means two URLs can serve one page.
Whatever you choose, make internal links, canonical tags and sitemap entries use the same form. See canonical URLs and duplicate pages.
Confirm: the non-preferred form redirects once and the preferred form returns 200.
Step 4: Add response headers where they belong
Why: headers carry security settings, caching rules and, when needed, an indexing instruction for non-HTML files such as PDFs.
Action on Netlify: use a _headers file or [[headers]] in netlify.toml.
/*
X-Content-Type-Options: nosniff
/downloads/*
X-Robots-Tag: noindex
Action on Vercel: use the headers array.
{
"headers": [
{
"source": "/downloads/(.*)",
"headers": [{ "key": "X-Robots-Tag", "value": "noindex" }]
}
]
}
One limit matters on Netlify. Custom headers apply to files served from the deploy. They do not apply to proxied content or to URLs handled by a Function or Edge Function, which must return their own headers. Netlify also ignores custom values for some headers its servers set themselves, such as Content-Length, Location and Server. Use redirects, not a Location header. For header choices, see HTTP security headers and X-Robots-Tag.
Confirm: curl -I on a matching URL shows the header exactly once.
Step 5: Keep previews and old deploys out of search
Why: a preview URL is a full copy of your site. If crawlers reach it, you get duplicates on a hostname you do not control.
Vercel: automatically adds X-Robots-Tag: noindex to preview deployments and to outdated production deployments. Assigning a production domain to a preview, or disabling the header in vercel.json, removes it, so do not do either by accident.
Netlify: adds X-Robots-Tag: noindex to Deploy Previews. Netlify recommends setting the project to private (credit-based plans only) or using Password Protection to keep previews out of search indexes, which is stronger than the header alone. Its own page says nothing about branch deploys, so check branch URLs yourself and protect them if they hold unfinished work.
A password is the most reliable control, because crawlers cannot fetch a protected page. Do not block previews in robots.txt and rely on noindex too; a crawler must be able to fetch the page to see the tag. Noindex vs robots.txt explains the difference.
Confirm: curl -I on a preview URL shows x-robots-tag: noindex, and the production domain does not.
Step 6: Check the 404 page, sitemap and robots.txt
Action: place robots.txt and your sitemap in the publish directory so they load at the site root. On Netlify, a 404.html at the site root is used automatically for paths that do not resolve to a file. Request a made-up URL and check that the status line says 404, not 200. On Vercel, run the same test against your framework's not-found page. If the status is 200, you have a soft 404; see soft 404 errors.
Confirm: /robots.txt and /sitemap.xml return 200 on the production domain, and a made-up path returns 404.
Final verification checklist
- One production domain answers with 200, and every other hostname redirects to it.
- Moved URLs return a permanent status once and land on a 200 page.
- No rule sends traffic in a loop or through more than one hop.
- Trailing-slash and clean-URL behaviour is set deliberately and matches your internal links.
- Headers appear once on the URLs you expect.
- Preview URLs return
noindexor are password protected. - A made-up URL returns 404.
robots.txtand the sitemap load from the production domain.
Common mistakes to avoid
- Forgetting the platform hostname. Redirect or noindex the default
*.vercel.appor*.netlify.appaddress. - Using
permanent: falsefor a lasting move. That sends a 307. Use true. - Putting
_redirectsor_headersoutside the publish directory. Netlify will not see them. - Writing a wildcard that matches everything. Vercel warns that this can send all requests to one destination.
- Expecting headers on function responses. On Netlify, functions must set their own headers.
- Fighting platform normalisation. Do not write rules to add or remove trailing slashes on Netlify.
- Letting a preview become the live site. Promoting or assigning production domains changes the
noindexbehaviour on Vercel. - Relying on the header for secrets.
noindexdoes not protect private work; use a password.
Frequently asked questions
Is 308 as good as 301 for SEO?
Google lists both as permanent redirects. Vercel's own page says 307 and 308 do not affect SEO and search engines treat them as normal redirects.
Can I redirect www to the bare domain on Netlify with a rule?
Yes, by writing the full source URL in the rule, but remember to cover both HTTP and HTTPS or force the redirect. You can also use the domain settings in the dashboard.
Do preview deployments hurt my site?
They can if crawlers reach them and treat them as separate copies. Both hosts add noindex to previews, and a password is stronger.
Do I need a sitemap for a static site?
Often a small, well-linked site does not strictly need one, but it helps with monitoring. Our guide on creating an XML sitemap covers it.
Related guides
- 301 vs 302 Redirects for SEO: Which Redirect to Use and When
- Redirect Chains, Loops, and Incorrect HTTP Status Codes
- X-Robots-Tag Header: Noindex PDFs, Images and Other Files
- Noindex vs Robots.txt: Which Keeps a Page Out of Search?
- Prevent Staging Site Indexing: Keep Staging Out of Search
- Add HTTP Security Headers: CSP, nosniff, Referrer-Policy and More
Official references
- https://docs.netlify.com/manage/routing/redirects/redirect-options/
- https://docs.netlify.com/manage/routing/headers/
- https://docs.netlify.com/deploy/deploy-types/deploy-previews/
- https://vercel.com/docs/project-configuration/vercel-json
- https://vercel.com/docs/headers/response-headers
Next step: See how Crawvix checks a website and capture a baseline once scanning opens. This guide provides technical information, not a guarantee of rankings, traffic or index inclusion.