Skip to content

The Preview Flag (Why I Check the Ad Destination Before a Campaign Spends)

GOOGLE ADS
The Preview Flag (Why I Check the Ad Destination Before a Campaign Spends)
Conner Crowe

Last week a landing page went live on a client’s ad destination with one meta tag set to preview instead of production. The page loaded. It looked finished. The form accepted a submission and returned a success panel. Underneath, the form never called the API, the analytics tags never loaded, and every lead would have died with the browser tab. The campaign was enabled against that URL. I caught it before a single click was paid for.

Quick Take

Landing pages built for paid traffic usually carry a switch that separates a preview build from a live one. When that switch is wrong, the page still works in every way a person can see. It renders, it takes a form, it says thank you. What it stops doing is recording. So I verify the destination before the campaign spends rather than after the first report comes back empty. Five of the six checks run from a terminal in under two minutes. The sixth needs a person.

What the flag does

The page carries a single line in its head:

<meta name="deployment-mode" content="production">

The page reads that value at runtime and gates everything measurable on it. Set to production, the form posts to the lead API and the GA4 and Google Ads tags load and fire. Set to anything else, the form short-circuits to a “Preview complete” panel and never reaches the network call. The tags load nothing at all.

The gate is one line of JavaScript, and its bluntness is the point:

if (mode !== 'production') { show(LAST + 1); return; }   // never reaches fetch()

That design is correct. A preview build should be incapable of writing a real lead into a client’s CRM, and it should be incapable of polluting a conversion action with test submissions. The flag exists so a staging copy can be circulated for review without any of it counting.

The failure mode is that the same property which makes preview safe makes it silent. There is no error state. Nothing turns red. A page in preview mode and a page in production mode are visually identical right through the thank-you screen. The only way to tell them apart is to read the tag or watch the network.

The morning I found it

The build was a paid-only page for a custom furniture maker taking commission work. Static assets on Cloudflare Pages, two Pages Functions behind them, leads flowing to WhatConverts.

I was doing a pre-launch pass on the live URL, not the preview URL, and I pulled the raw HTML rather than looking at the rendered page. That is the only reason I saw it. deployment-mode read preview. The Search campaign pointed at that exact URL and was already enabled.

Two other things surfaced in the same pass, both from the same deploy. The first: the repo root had been deployed instead of the public/ directory, so README.md and .gitignore were being served at the site origin and both returned HTTP 200 on a live client site. The second: wrangler resolves the functions/ directory relative to the working directory rather than the asset directory you hand it, so a deploy run from the wrong folder ships the static assets without the Functions. The tell is that /api/lead starts answering 404 where it should answer 405.

Then a fourth problem, which is the one worth remembering. The apex domain runs on Google Cloud DNS, so it is not a Cloudflare zone. No caching panel, no zone id, no purge_cache call. Deleting the stray README.md did not evict it. The edge kept serving the deleted file with CF-Cache-Status: HIT under a seven-day TTL. The fix was to overwrite the path with new content so the ETag changed, which replaced the cached response immediately.

The check that runs before the campaign

This is now a script rather than a memory. It runs against the live URL, never the preview URL, and it fails loudly:

  1. Read deployment-mode from the served HTML. Not the local file, not the preview origin. Fetch the URL the ad points at.
  2. Confirm both tag IDs and the conversion label are present in the served markup, and that the bot-protection site key is there too.
  3. Confirm the lead endpoint shipped. GET /api/lead should answer 405. A 404 means the Functions did not deploy and the form has nowhere to post.
  4. Confirm the production secrets exist on that environment. Preview and production are separate environments, and secrets set on one do not exist on the other. POST deliberately malformed JSON to the endpoint. invalid_json means the secrets are wired. lead_system_not_configured means they are missing and the handler is failing closed.
  5. Check every anchor the ads reference. Sitelinks and price extensions point at fragments. A renamed section turns a sitelink into a scroll to nowhere.
  6. Submit one real lead by hand. Confirm it lands in the lead system, that the notification email arrives, and that a conversion records against the right action. No script can do this part. Bot protection is enforced server-side, which is the whole point, so a human has to be the one who proves the path works end to end.

Steps one through five are automated and take seconds. Step six is the only one that proves anything, and it is the one most launches skip.

What skipping it costs

The cost is not a bad campaign. It is an invisible one.

Traffic arrives. Clicks bill. People fill out the form and see a confirmation, so nobody complains. The leads never reach the CRM and the conversions never reach the ad platform, which means Smart Bidding is optimizing against an empty signal and steering budget by nothing at all. Every day it runs, the bidding model learns from noise.

The report at the end of the month shows spend and clicks and zero conversions, and the conversation that follows is about whether the offer is wrong or the keywords are wrong. That is a hard hole to climb out of, because the account now has a stretch of history that says the traffic does not convert, and none of it is true. I wrote about that exact symptom in clicks with no conversions. A misconfigured destination produces the same chart as a genuinely bad campaign.

The receipts

The flag was found on 2026-08-05 on a live Cloudflare Pages deployment with the Search campaign already enabled. The stray README.md at the origin returned HTTP 200 and kept serving from cache under s-maxage=604800 after deletion, which is what forced the overwrite-instead-of-delete fix. /api/lead returned 404 until the second deploy shipped the Functions bundle, then 405 as expected. The verification script now checks the deployment flag, both tag IDs, the conversion label, the bot-protection key, all seven sitelink and price anchors, and the endpoint status on every relaunch. Conversions on that page fire only after the API returns a lead id, so the conversion count can never exceed the leads delivered. The measurement architecture underneath is the same one in the Tracking Stack.

One thing to do next

Pull the HTML of the page your ads point at right now and read it. Not the rendered page. The source. If you want the longer version of that pass, the Google Ads Setup Audit is the twenty-five page checklist I run on accounts before I touch anything, and it is free with no email required.

Want a review like this on your account?

Want this kind of review
on your account?

Thirty minutes on the phone. Same person on the call as on the work. Walk out with a clear set of next steps.

Book a Call