Syndicate to a 404 and you donate the SEO
A canonical that 404s when the syndicating platform fetches it does not transfer authority. Their copy becomes the original, and the order is the whole fix.
Cross-posting is meant to be free reach: publish on your own site, syndicate to a larger platform, and a canonical link tells search engines which copy is the original.
That only works if the original is already there when the platform goes to look.
The case this post is about
One post, published to its own site and then syndicated, in that order — and the three steps where the order is load-bearing:
deploy the site, and wait for it to be live
verify fetch the canonical URL yourself — a 200, not a redirect chain
syndicate only now create the copy on the other platform
Run those out of order and nothing errors. The syndicated copy is created, the canonical tag is in its head, and the platform fetched that URL while it was still a 404 — so it recorded no original, kept the authority for itself, and the copy is now the version search engines rank.
This post follows one post through those three steps, because the failure is silent at every point and only observable months later in which URL ranks.
The symptom
The syndicated copy outranks your own. Somebody searches a phrase from a post you wrote, and the result is the platform's version — same words, their domain, their ad surface, and a link back to you that a fraction of readers will follow.
The second thing is that every individual step looked correct. The post has a canonical field. The platform accepted it. The syndication tool reported success. Nothing in the workflow produced an error, because the only thing that went wrong is a sequence that nobody declared.
The third is the window, which is narrow and unobserved. A platform fetches the canonical target when the post is created — once, at that moment. If the page is not live yet, the fetch fails, and the failure is on their side of the wire where you never see it. Deploy five minutes later and the page is fine forever, and the claim was already discarded.

The fourth is that it is effectively permanent. Authority accrues over time to whichever copy the index settled on, and by the time the ranking is visible the decision is old. Fixing the canonical afterwards is correct and does not undo the months during which the other copy was the one being cited and linked.
There is a fifth that only shows up on a corpus. One post getting this wrong is a curiosity; a hundred posts syndicated by the same workflow get it wrong the same way, so the pattern is not a scattering of odd results but a whole body of work whose authority sits on somebody else's domain — discovered all at once, long after the ordering could have been fixed for free.
Why the obvious fix failed
Set the canonical field and trust it. What everybody does, and the field is a request rather than a declaration. A canonical pointing at a URL that does not resolve is not a weaker claim; it is no claim, because there is nothing at the other end to be canonical.
Publish both at the same time. The instinct, and "the same time" is doing a lot of work. A static site has a build and a deploy between the commit and the live page, which is minutes. The platform's fetch happens in seconds. Simultaneous in intent is sequential in fact, and in the wrong order.
The tell that this is what happened is that it is intermittent in the least helpful way: it works whenever the deploy happens to be fast and fails whenever it is slow, so the same workflow produces both outcomes and neither is reproducible on demand.
Check the canonical afterwards. Worth doing and it verifies the wrong moment. The page resolving now says nothing about whether it resolved during the fetch, and the check will pass cheerfully on every post that got this wrong.
Let the platform be the original. A legitimate strategy and a different one. If the platform is where you want the authority, this is all fine — the failure described here is specifically getting the other outcome by accident while believing you chose this one.
That distinction is worth holding onto, because it is the one that decides whether any of this matters to you. Two publishers can run identical workflows and only one of them has a bug, and the difference is entirely what they intended. The failure is not the outcome; it is that the outcome was decided by deploy latency rather than by anybody.
The actual mechanism
Make the order explicit: deploy, verify the canonical resolves, then syndicate.
deploy the site, and wait for it to be live
verify fetch the canonical URL yourself — a 200, not a redirect chain
syndicate only now create the copy on the other platform
The second line is the one that turns a convention into a check. Fetching the URL you are about to declare as canonical is a two-second operation that distinguishes "should be live" from "is live", and those differ exactly when a deploy is slower than usual — which is the case that produces the bug.
The sub-case worth knowing is the redirect. A canonical that resolves via a redirect is weaker than one that resolves directly, and trailing-slash mismatches are the common source: the frontmatter says one form, the site serves the other, and the canonical points at a URL that works for a human and is one hop from the one the platform will store.
There is a related ordering question people get backwards on updates. Editing the original is safe in any order; editing the syndicated copy to match is where drift starts, because now two copies are maintained and only one is the source. The copy should be regenerated from the original rather than edited, for the same reason the canonical points the way it does.
Regeneration has a cost people underestimate, which is why this slides. Most syndication platforms have no update path that preserves the post's identity cleanly, so "regenerate" in practice means delete and recreate — and on a platform with no delete endpoint it means doing it by hand. The honest position is that the copy is close to immutable once made, which is another reason to get the original right before it exists.
The fix
deploy first, always, and wait for live
verify a direct 200 on the exact canonical string, no redirect
syndicate after both, one post at a time
never edit the copy; regenerate it from the original
The third line has a second reason behind it that is not about SEO. Most syndication APIs have no delete endpoint — a post created in error can only be removed by hand in the platform's interface — so batching creates turns one ordering mistake into a batch of permanent ones.
The ordering as code, with the verify between the two halves where it belongs:
async function syndicate(slug: string, canonical: string) {
// 1. DEPLOY, and wait for it to be live. Not "kick off a deploy".
await run("idea-site", ["deploy-prod", "--project", PROJECT]);
// 2. VERIFY the exact canonical string, no redirect. A 301 to the right page is
// NOT good enough: the platform stores the URL it was given, and if that one
// redirects today it may 404 later, by which time the copy has outranked us.
const res = await fetch(canonical, { redirect: "manual" });
if (res.status !== 200) {
throw new Error(`canonical not live: ${canonical} → ${res.status}; refusing to syndicate`);
}
// 3. SYNDICATE, one at a time. No delete endpoint exists, so a batch turns one
// ordering mistake into N permanent ones.
const ledger = `.publish-out/${slug}.ledger.yaml`;
if (await exists(ledger)) return { skipped: "already syndicated" };
const post = await createRemotePost({ slug, canonical });
await Deno.writeTextFile(ledger, toYaml(post)); // the record that makes the skip work
return post;
}
Run against a slug whose site deploy has not landed:
$ syndicate three-phases
error: canonical not live: https://blogs.example.com/blog/three-phases/ → 404;
refusing to syndicate
That refusal is the whole post. The alternative is a remote copy whose rel=canonical points at a
404, which makes the copy the original as far as any crawler is concerned.
What makes this cheap is that it is pure sequencing. There is nothing to build, no service to add and no ongoing cost; the entire fix is that two steps that were being run in whichever order was convenient now run in a declared one, with a check between them.
The cost is a slower publish, and it is worth naming because it is the reason people skip it. The verify step adds the deploy's own latency to every post — a minute or two of waiting that does nothing visible. That is the whole price, and it buys the difference between owning the ranking for your own writing and renting it.
How we proved it
The mechanism is checkable before you need it. Request the canonical URL of an unpublished post and look at the status. A 404 is what the platform's crawler would receive, and seeing it yourself is the whole argument for why the canonical field alone is not a guarantee.

The order is encodable, which is what makes it hold. Once deploy-then-verify-then-syndicate is a single command rather than three habits, the sequence stops depending on whoever is doing it being unhurried — and the verify step gives a reason to stop that is specific enough to act on.
What I cannot tell you is how much authority was actually lost, for the posts where this went wrong before I noticed. Ranking attribution is not something I can isolate from everything else that changed, and anybody offering you a number for this is modelling rather than measuring.
The check is worth running retrospectively even so, because it is cheap and it tells you the shape of the problem. Fetch the canonical of every syndicated post and count the non-200s: that does not quantify the lost authority, and it does tell you whether this happened to three posts or to the whole corpus, which is the difference between an incident and a workflow defect.
What I have not solved: the platforms that ignore the canonical entirely. The field is a request, honoured by convention and by the major engines; a platform that does not pass it on, or an engine that overrides it on its own signals, is outside what this controls. The ordering makes the claim valid — it does not make it binding.
And a limitation worth stating: none of this helps if the original is thin. Canonical ordering decides which copy gets the credit; it does not create the credit. A post that nobody links to ranks nowhere under either domain, and the ordering discipline matters most precisely for the posts that were going to do well.

One thing I would do differently is verify at the exact string rather than the page. My first check fetched the post's URL and called it resolved, which passed on a canonical that differed by a trailing slash and was therefore one redirect away from the thing the platform stored. The check has to use the literal value from the frontmatter, byte for byte, because that is what gets sent.
The thing that reframed it for me was realising a canonical link is not metadata about the page, it is a claim about a relationship between two URLs — and like any claim about a relationship, it fails if one end is missing at the moment it is evaluated. Once the field is read as a claim with a verification time rather than as a tag, the ordering requirement is obvious and the old workflow looks plainly wrong.
What this generalizes to
A declaration about something is only as good as that thing existing when the declaration is read, and the gap between writing and reading is where this class of bug lives.
The same shape appears all over. A webhook registered against an endpoint that is not deployed yet. A manifest naming an artifact version that has not been published. A DNS record pointing at an origin that is not serving. A deep link to a screen that ships next release. Each is a correct statement about a future state, evaluated by somebody else at a moment you did not choose.
So the useful question for any outward-facing reference is when does the other side look, and what is there at that instant — because the answer is almost never "whenever I am ready", and the ordering that makes it safe is usually free if you decide it deliberately rather than discovering it.
One post a week, no digest
New writing when it lands. No roundups, no course pitch, no "10 tips".