郭立 (leeguoo)

# I Deleted a Post, but It Wouldn’t Disappear from Google: A Redirect Had Welded a Dead Link in Place

I deleted an old blog post. The zh version returned a clean 404, but the en and ja URLs still returned 302 redirects to that already-404 zh page, so Google kept them in its index. The root cause wasn’t that the post hadn’t been deleted cleanly. It was a multilingual fallback redirect that trusted a “task table” without checking whether the source page was still live, welding the dead link into the search engine. This post reviews the symptoms, investigation, root cause, small fix, and two lessons about deletion and redirects.

Jul 2, 2026 · Posts · Public · Article

ON THIS PAGE

A few days ago, while cleaning up my blog, I deleted an old post that should not have been public. After deleting the zh version, visiting it directly returned a clean 404, so I thought that was the end of it. But it kept lingering in Google, and the en and ja URLs still seemed “alive” when clicked. I had deleted it, so why couldn’t I get rid of it?

After finding the root cause, I didn’t know whether to laugh or cry: the issue wasn’t that the post hadn’t been deleted cleanly, but that a multilingual redirect had welded this dead link into the search engine. I’m writing it down because this kind of “zombie URL” is easy to run into, and its symptoms are misleading.

Deleted content becomes a 404 tombstone, but a 302 redirect signpost is still sending people into a dead end; the fix is to add a checkpoint before redirecting, confirming the destination is still live before deciding whether to redirect or return 404

Symptom: zh Was 404, but en/ja Were 302

This blog is trilingual, and each post has zh / en / ja URLs. The deleted one was the zh version. Running curl showed:

$ text
zh  → 404                          (gone, correct)
en  → 302 → /zh/posts/<slug>/      (redirects to the just-deleted zh)
ja  → 302 → /zh/posts/<slug>/

The en / ja URLs were still there, and they returned 302 redirects to the zh page that was already 404. For users, the experience was “redirect once, then 404.” For Google, the signal was “these two URLs still exist; they just redirect,” so it kept them in the index. The content was deleted, but the shell around it was not.

Investigation: A General Rule, or Specific to This Post?

First I needed to separate one thing: did all /en/posts/* paths redirect to /zh/? If it was a general fallback rule, then this would just be “dead link redirects to dead link,” not especially meaningful. So I tried a slug that had never existed:

$ text
en/a-completely-nonexistent-slug   → 404
en/<this-posts-slug>               → 302 → zh

That exposed the difference: a made-up slug returned 404 directly, while only this post’s en URL returned 302. So it wasn’t a general rule. There was a leftover record on this specific path telling it to redirect to zh.

Then I confirmed whether the content layer had really been deleted. I downloaded all three language versions of the post through the publishing API, and all of them reported “not found.” It also no longer appeared in the sitemap. The content was truly gone. The problem was somewhere else: the redirect record had outlived the content.

Root Cause: The Redirect Trusted the “Task Table” Without Checking Whether the Content Still Existed

The blog’s multilingual logic is: “if a language is missing, fall back to the one that exists.” When rendering /en/posts/<slug>, if the en version does not exist, the worker checks a localization task table that records things like “this post’s source language is zh, and it should be translated into en/ja.” If it finds that the source is zh, it returns a 302 redirect to /zh/posts/<slug>.

The problem was here: it trusted the “source is zh” record in the task table, but never verified whether that zh page was still live. When the zh content was deleted, the localization task record was not deleted along with it. So the source was gone, the record remained, and the redirect still fired, sending everything through a 302 to a 404.

In one sentence, the bug was this: the redirect pointed to “where the content should be,” not “where the content actually is.” Once the content is deleted or moved, this kind of redirect becomes a welded-in dead link.

Fix: Before Redirecting, Confirm the Destination Is Live

The change was small, and the direction was clear: before deciding to return a 302, first check whether the source page being redirected to is actually still published, not private, and not deleted. I added an isPublishedPostLive() guard:

$ text
Is the source page still live?   Yes → keep returning 302 as before (normal trilingual fallback)
                                 No  → return 404 (don’t send users and crawlers into a dead end)

After deployment, another curl showed that en / ja now correctly returned 404. Normal multilingual fallback, where the source still exists, was unaffected. I specifically verified this with another article that only has a zh version: its en URL still returned 302 as expected, with no collateral damage.

Two Lessons to Take Away

First, a 301/302 to a 404 is worse than a direct 404. A direct 404 tells Google, “this is gone; remove it from the index.” But with “redirects to a 404,” Google may keep observing the redirecting URL, making the dead link stickier. If you want a page to disappear completely, let it return a clean 404 or 410 by itself. Don’t add a redirect layer to “handle it gracefully.”

Second, deletion has to cascade to every derived artifact. When a piece of content is deleted, the things connected to it include more than just the body: redirect records, localization pairings, sitemap entries, and caches. Miss any one of them, and you get another entrance to a zombie URL. In my case, the thing that didn’t get deleted was the localization task table entry. When writing deletion logic, make the checklist complete. A sturdier fallback is the same idea as this fix: make every “redirect/reference” verify that its destination is still alive before using it, rather than assuming it must exist.

Deleting things is harder than it seems. The standard for a truly clean deletion is not “the main record is gone,” but “every path that could reach it can no longer get there.”

← previous
How to Build a Good AI Girlfriend
next →
One Real Chrome, Several Agents, and You: How to Keep Everyone from Clicking Each Other’s Stuff

Comments

Replies are public immediately and may be moderated for policy violations.

Max 1000 characters.