The ad slots on this blog are finished and render nothing
Nine ad placements built, gated and verified to render nothing. What you can decide before approval is where ads may go, and that is the decision that matters.
This blog has nine advertising placements. They are implemented, deployed, and verified to render exactly nothing — zero elements, on every page type.
The revenue is zero, which is the honest state and is not the interesting part.
The case this post is about
Nine ad placements, fully built, wired, tested — and rendering nothing:
type AdConfig = { publisherId?: string; slotId?: string; consent?: "granted" | "denied" };
export function AdSlot({ config, placement }: { config: AdConfig; placement: string }) {
const missing = (["publisherId", "slotId"] as const).filter((k) => !config[k]);
// Render NOTHING — not a placeholder, not a bordered box, not reserved space.
The site is not approved for ads yet. Approval is somebody else's decision on somebody else's timeline, and nothing about building the surface makes it arrive sooner.
So the nine placements exist and are dark, gated on three values that are all absent. This post follows that gate, because the tempting version renders a bordered empty box — which looks broken to a reader and looks like an attempt to a reviewer.
The symptom
The post somebody wants to read is "what a technical blog with a hundred posts earns", and almost every version of it is written by somebody selling a course about technical blogs. The numbers are rarely attached to a verifiable site, and the ones that are tend to come from people whose traffic arrived by a route they are not describing.
The second thing is that the question is asked too early in the wrong direction. The useful version is not "how much does it earn" but "what did you have to decide before anybody would let you find out", and that part is entirely knowable in advance — which makes it the half that can be written honestly without any revenue at all.
The third is what happens if you skip that thinking. Ad placement is one of the few decisions where the lazy option actively damages the asset: a slot above the fold converts best and tells a first-time reader what kind of site they have arrived at. For a blog whose entire return is whether somebody believes the writing, that trade is not close, and it is irreversible in the sense that the impression is formed before they read anything.

The fourth is the thing I did not expect. Building the slots while they cannot run turns out to be the right order, because every placement decision is made while there is no revenue to argue with. A slot that is dark costs nothing to move. The same decision, revisited after the slot has been earning for six months, is a different conversation with a different incentive.
Why the obvious fix failed
Wait until approval, then add the slots. The intuitive sequence and it puts every placement decision at the moment of maximum pressure to monetise. Approval arrives, the obvious thing to do is turn on the slots that earn most, and the considered version of the layout never gets built.
Copy a layout that works. Most high-performing layouts are performing for sites whose value is the traffic rather than the credibility. Importing those positions imports an assumption about what the site is for, and that assumption is usually wrong for a publication whose readers are evaluating whether the author knows anything.
Put one slot in and see. Reasonable experimentation and it does not survive its own result, because the measurement is immediate revenue and the cost is a slow change in how the site is perceived. One of those shows up in a dashboard next week and the other shows up never, in the form of people who did not come back.
That asymmetry is what makes incremental experimentation untrustworthy here specifically. Experiments work when the thing you measure is the thing you care about; this one measures revenue and risks credibility, so every individual result points the same way and the accumulated effect points the other. The discipline has to come from a decision made outside the experiment.
Treat on-site ads and platform partner programmes as the same decision. They are not. A platform's partner payout and advertising on a domain you own have different economics, different ceilings and different relationships to the work — conflating them produces a single verdict about "ads" that is wrong about one of them.
I had made exactly that mistake, and the tell was that I had a settled opinion covering both. A conclusion that answers two questions at once is usually answering one of them by accident, and the one it is wrong about tends to be the one you looked at second.
The actual mechanism
Decide where advertising may go as a design constraint, implement it fully, and gate it so that it renders nothing until the day it is permitted.
placements nine, across the page types — none above the fold
constraints nothing inside the reading measure of a post
nothing on a page people pass through in seconds
gating three values must all be present or the slot renders nothing
verify a browser check confirming zero slot elements on every page
The constraint lines are the content of the decision, and each one came from asking what the page is for. A post page's job is to be read, so nothing interrupts the measure the text is set in. A wayfinding page — a topic hub somebody is passing through to get somewhere — is measured in seconds, so an advertisement there is interrupting navigation rather than reading. Above the fold is excluded everywhere, because that position is what the site says about itself before any content loads.
Those three constraints have a property worth noticing: each one is stated in terms of what the page is for rather than in terms of advertising. That is what makes them hold when somebody later proposes an exception, because the argument is not about ads at all — it is about whether this particular page is still allowed to do its job.
The sub-case that makes this safe to ship is the gating. Three separate values have to be present for any slot to render: the feature flag, the publisher identifier and the slot identifier. Any one of them absent and the component emits nothing at all — not an empty container, not a placeholder, no layout shift. That is what makes "built and dark" a real state rather than a half-finished one.

There is a related detail worth copying: the file that declares the publisher to the ad network is generated from the same configuration block as the slots. Those two agreeing is not something anybody should have to remember, and they are exactly the kind of pair that drifts silently and fails in a way that is hard to diagnose.
The consent surface is the other half that has to be built before anything can be switched on, and it is easy to treat as paperwork. It is not: it is a thing readers interact with on arrival, it occupies screen space, and its behaviour on a second visit is a real design decision. Building it late means designing the most visible component of the whole system in a hurry.
The fix
decide the placement constraints first, as a design question
build all of it, completely, including the consent surface
gate on every required value, rendering nothing when any is absent
verify mechanically, in a browser, that nothing appears
wait approval is somebody else's decision and not an engineering task
The gate is the interesting part: the component renders nothing at all unless every required value is present, so an unapproved or half-configured state cannot produce a broken slot.
type AdConfig = { publisherId?: string; slotId?: string; consent?: "granted" | "denied" };
export function AdSlot({ config, placement }: { config: AdConfig; placement: string }) {
const missing = (["publisherId", "slotId"] as const).filter((k) => !config[k]);
// Render NOTHING — not a placeholder, not a bordered box, not reserved space.
// A visible empty slot on an unapproved site is worse than no slot: it looks broken
// to readers and looks like an attempt to the reviewer.
if (missing.length > 0) return null;
if (config.consent !== "granted") return null;
return <ins className="adsbygoogle"
data-ad-client={config.publisherId}
data-ad-slot={config.slotId}
data-ad-format="auto" />;
}
And the check that it really renders nothing, in a browser, rather than by reading the code:
const page = await open("/blog/three-phases/");
const slots = await page.locator("ins.adsbygoogle, [data-ad-slot]").count();
const reserved = await page.locator('[class*="ad-"]').evaluateAll(
(els) => els.some((e) => e.getBoundingClientRect().height > 0));
console.log({ slots, reservedSpace: reserved });
$ deno task verify-ads
{ slots: 0, reservedSpace: false } # unapproved: nothing rendered, no layout shift later
reservedSpace: false is the half people miss. A slot that renders nothing but still reserves height
leaves a gap readers see now and a layout shift when it eventually fills.
The last line is the one that is uncomfortable and clarifying. There is nothing to build, optimise or work around; the thing that stands between the apparatus and the revenue is an external review with its own criteria and timeline.
Recognising a blocker as external rather than technical is what stops
weeks going into activity that feels like progress.
What this leaves is a question I can answer and a question I cannot. Where should advertising sit on a publication whose return is credibility — answerable, and answered above. What does that earn — not answerable yet, by me, with evidence.
How we proved it
The darkness is mechanically verified. A browser check across the page types confirms zero slot elements render — not hidden, not empty, absent. That is a property worth testing rather than assuming, because "gated" is a claim about code paths and "nothing renders" is a claim about the page, and the second one is what readers experience.

The placement constraints are checkable against the live pages. Nothing above the fold, nothing inside a post's reading measure, nothing on the navigation hub — all three are observable by looking, which is the right standard for a decision that is fundamentally about what the site looks like.
What I cannot tell you is the number, and I want to be exact about why. The topic entry for this post has a note on it saying not to draft it until there is a month of revenue and traffic data, because without that it would be theory. That note is correct about the earnings question. I have written the placement half instead and left the economics unstated rather than estimated, because an estimate from somebody with zero data is the thing the original complaint was about.
What I have not solved: whether the constraints cost anything meaningful. Excluding the highest-performing positions presumably reduces revenue, and I have no measurement of by how much — so my claim is that the trade is right in principle and not that I have priced it. It is possible the cost is large enough that somebody reasonable would choose differently.
And a limitation of the whole exercise: nine placements on a site with modest traffic is, in all likelihood, not a revenue line. The honest expectation for advertising on a niche technical blog is small, and the reason to build it carefully is that it is on a domain I own, where the traffic, the search position and any revenue all accrue to the same asset — not because the figure is going to be interesting.
The thing I would tell somebody is to do the placement work while it costs nothing to be strict. Every constraint above would have been harder to adopt against live revenue, and several of them would probably not have survived the conversation.
What this generalizes to
A decision made while the stakes are zero is a different decision from the same one made under pressure, and the sequence is often yours to choose.
The pattern runs well beyond advertising. Rate limits set before anybody is complaining about them. Data retention decided before there is data worth keeping. Permissions scoped before anybody is blocked by them. Deprecation policy written before the first deprecation. In each case the decision is cheap and abstract early, expensive and personal later, and the quality of the answer is almost entirely determined by which of those two moments it was made in.
So when something is blocked on an external approval, the useful question is which decisions this blocker is currently making free. The waiting is not dead time; it is the only window in which the principled version of the answer costs nothing to adopt — and it closes the moment the thing is switched on.
One post a week, no digest
New writing when it lands. No roundups, no course pitch, no "10 tips".