Staging site indexed by Google: fix a Next.js + Sanity setup
By Nayan Kyada · · 6 min read
Part of The Sanity + Next.js Guide
If your staging site is indexed by Google, you will usually find out the awkward way: a colleague searches your brand and sees staging.yoursite.com next to the real result, with placeholder copy or half-finished pages. On Next.js and Sanity builds this is a configuration gap, not a bug, and it is fixable in an afternoon. The cleanup takes longer than the fix, so the order of steps matters.
Symptoms that your staging site is leaking into search
The signs are usually one of these:
- A
site:staging.yoursite.comsearch in Google returns results. - Search Console shows a property or a set of URLs you do not recognise, or the Pages report lists "Duplicate, Google chose different canonical than user".
- Your real sitemap contains staging hostnames, or the staging sitemap is submitted by mistake.
- Analytics shows sessions landing on a staging hostname from organic search.
- A preview or staging URL appears in a competitor's backlink report because someone shared it publicly.
Staging pages rarely rank above production for long. The real damage is quieter: duplicate content, split link signals, and Google occasionally choosing the wrong host as canonical for a page.
What it costs to ignore it
Three things get worse the longer it sits.
First, canonical confusion. If staging pages declare themselves canonical, or have no canonical at all, Google has to guess. On a site that is mid-redesign this is exactly when you cannot afford guessing. The redesign without losing SEO checklist covers why staging hygiene is part of launch safety.
Second, content exposure. Staging often carries unpublished drafts, test pricing, or client-confidential copy. If your staging app reads draft content from Sanity, anything an editor typed is a search result waiting to happen.
Third, crawl budget on larger sites. Google spends time on a duplicate host instead of discovering your new pages.
Causes, in the order I check them
1. The staging deployment is public and serves normal robots rules
The most common cause: staging is a normal deployment on a custom subdomain, with the same robots.ts and metadata as production. Vercel adds a noindex header to its default preview deployment URLs, but a custom domain you attach to a branch is your responsibility to verify. Do not assume it is covered. Check the response headers with curl and look for an X-Robots-Tag header.
curl -sI https://staging.yoursite.com/ | grep -i -E "x-robots-tag|HTTP"If nothing prints for x-robots-tag and the page has no robots meta tag, staging is indexable.
2. robots.txt is the only protection
A Disallow: / rule is better than nothing, but it only stops crawling. If a staging URL was linked from anywhere, Google can still list it without a snippet. Worse, if you added Disallow after the pages were indexed, Google can no longer fetch them to see a noindex tag, so they linger.
3. Canonical URLs and sitemap point at the wrong host
If metadataBase is hardcoded or built from the request host, staging pages emit staging canonicals and the staging sitemap lists staging URLs. This is how a staging site becomes a legitimate-looking duplicate. Also check structured data: JSON-LD with a hardcoded url can point either direction.
4. Shared Sanity content with no environment awareness
One dataset feeding both environments is fine, but the app needs to know which environment it is. If you also use a single dataset and editors publish to it, staging shows live content immediately, which makes it look like a real site. My notes on splitting staging and production on one dataset go deeper on that side.
The fix: make the environment explicit in code
Define one environment variable per deployment, for example SITE_ENV set to production or staging, and derive everything from it. I prefer my own variable over relying on VERCEL_ENV alone, because custom domains and custom environments do not always map cleanly to what you think they do.
// src/lib/site.ts
export const isProduction = process.env.SITE_ENV === 'production'
export const siteUrl = isProduction
? 'https://www.yoursite.com'
: (process.env.NEXT_PUBLIC_STAGING_URL ?? 'https://staging.yoursite.com')Then wire it into robots, the root metadata and the sitemap host.
// src/app/layout.tsx
import type { Metadata } from 'next'
import { isProduction } from '@/lib/site'
export const metadata: Metadata = {
metadataBase: new URL('https://www.yoursite.com'),
robots: isProduction
? { index: true, follow: true }
: { index: false, follow: false, nocache: true },
}Note that metadataBase stays pointed at production on purpose. Staging pages then emit canonicals to the live site, which is the safe default if a staging page ever does get crawled. Pair this with a robots.ts that returns disallow: '/' when not production, and a sitemap route that returns an empty list outside production.
The code is the second layer. The first layer is access control: put staging behind Vercel's deployment protection or HTTP basic auth. A page that returns a login wall cannot be indexed, and it also stops draft content from being read by strangers.
Cleaning up what is already indexed
Order matters here:
- Ship the noindex and canonical changes and confirm with curl that the header and tags appear.
- Do not add a robots.txt Disallow yet. Let Google recrawl and see the noindex first.
- Verify the staging hostname as its own property in Search Console and use the Removals tool to temporarily hide the URLs while the noindex takes effect.
- Once the pages have dropped out, add authentication on top so it does not happen again.
- Remove any staging hostnames from the production sitemap and confirm the live sitemap only lists the real domain.
The temporary removal in Search Console lasts months, not forever. The noindex or the login wall is what makes it permanent.
How to verify it worked
- Run curl against five or six staging URLs and confirm
x-robots-tagor a robots meta tag says noindex, or that you get a 401. - View source on a staging page and confirm the canonical points to production.
- In Search Console, inspect a staging URL with URL Inspection and look for "Excluded by noindex tag" or a blocked access result.
- Check
site:staging.yoursite.comagain in a few weeks. It should trend to zero. - If production pages were affected, watch the Pages report for the duplicate canonical warnings clearing. For anything that still is not indexing after that, see Next.js pages not indexed by Google.
What to ask a developer you hire for this
- How will staging be protected, by authentication or just by a tag?
- Where does the canonical host come from, and is it hardcoded or environment-driven?
- Will the sitemap and JSON-LD be checked for the wrong host?
- What happens to draft content on staging, and who can see it?
- How will you confirm the cleanup in Search Console, rather than just saying it is done?
A vague answer to any of these usually means the same problem returns on the next environment someone adds.
This is work I do: a staging and indexing review is usually part of my fixed-price Performance & Sanity audit, which starts from $800. You can see the packages at /services#packages.
Frequently asked questions
01How do I remove a staging site from Google search results?
First block it properly with authentication or a noindex header, then use the Removals tool in Google Search Console to hide the URLs quickly. Do not rely on robots.txt alone, because a blocked page can stay in the index if Google never gets to see the noindex.
02Will a staging site indexed by Google hurt my production rankings?
It can. Duplicate copies of your pages split signals, and Google may pick the staging URL as the canonical version for some pages. The risk is highest when staging has no canonical tags pointing at production.
03Should I use robots.txt or noindex to hide a staging site?
Use noindex or, better, password protection. A Disallow rule stops crawling but does not remove URLs already indexed, and it prevents Google from reading a noindex tag on those pages.