Stop Serving Soft‑404s: How to Retire Expired Job Pages on Firebase Hosting Without Hurting Your SEO

Job listings disappear every day. If a deleted job URL still returns HTTP 200 (a “soft 404”) or redirects to an irrelevant page, Google wastes crawl budget, keeps the ghost URL indexed, and your Search Console fills with warnings. This article walks through the exact configurations—tested on vacatures.today—to make Google drop those pages quickly without hurting your ranking signals.

1 | Why Soft‑404s Are Silent SEO Killers

ScenarioWhat Google SeesConsequence
Deleted job still returns 200 with “page not found” text Soft 404 URL remains in index, crawl budget wasted, “Soft 404” warnings
Deleted job 301‑redirects to the homepage Misleading redirect → Soft 404 Same as above + link equity is lost
Deleted job returns 404 or 410 Clear “gone” signal Google drops it fast & frees crawl budget

2 | What Firebase Hosting Can & Can’t Do

Hosting featureStatus codes it can emit
Static file or rewriteAlways 200 OK
redirect rule3 xx only (301, 302, 303, 307, 308)
Cloud Function / Cloud Run via rewrite → functionAny code (404, 410, 451…)

3 | Configuration Playbook

A. Pure Static 404 (No Functions Needed)

If you just want real 404s and have no need for a 410, delete any wildcard rewrites. Firebase automatically serves 404.html with a correct 404 status when no file or rule matches.

{
  "hosting": {
    "public": "public",
    "headers": [ /* your security headers … */ ]
  }
}

B. Static 410 Work‑around (Returns 200 + noindex)

This keeps hosting static but relies on X‑Robots‑Tag: noindex to de‑index URLs. Google obeys, yet the status code is still 200.

{
  "hosting": {
    "headers": [{
      "source": "/jobs/**",
      "headers": [{ "key": "X-Robots-Tag", "value": "noindex" }]
    }],
    "rewrites": [{
      "source": "/jobs/**",
      "destination": "/410.html"
    }]
  }
}

C. True 410 with One Cloud Function

The gold‑standard: Google sees an immediate 410 Gone, crawl budget is freed fastest.

firebase.json

{
  "hosting": {
    "public": "public",
    "rewrites": [
      { "source": "/jobs/**", "function": "gone410" }
    ]
  }
}

functions/index.js

const functions = require("firebase-functions");
const path = require("path");

exports.gone410 = functions.https.onRequest((req, res) => {
  res.status(410).sendFile(path.resolve(__dirname, "../public/410.html"));
});
Minimal Cloud Function cost: one region, ~128 MB memory → pennies per month, and no cold‑start issue for a simple sendFile.

4 | Crafting the Perfect Gone Page

  • Return 404 or 410 status, not 200.
  • Add <meta name="robots" content="noindex">.
  • Explain the job is closed and link to current vacancies.
  • Keep it lightweight; Googlebot skips heavy JS on error pages.

5 | House‑keeping Checklist

  • Remove expired URLs from your XML sitemap immediately.
  • Scrub internal links (search results, related‑jobs widgets).
  • Use Search Console’s Removals tool only for urgent/legal takedowns; 404/410 is enough for routine expiry.

6 | Conversation Log & Examples

The questions that sparked this guide, with answers in‑line:

User Q 1: “Now we will configure Firebase for 401 if pages are removed.”

A: 401 means “login required”, not “gone”. Use 404 or 410.

User Q 2: “No, I mean 410.”

A: Perfect—410 is the ideal signal for permanently removed content.

User Q 3 (config excerpt):
{ "rewrites": [{ "source": "**", "destination": "/404.html" }] }

A: That returns 200 OK (soft‑404). Delete the rewrite or use a Cloud Function.

User Q 4: “Is it possible to do it without a function?”

A: Firebase Hosting can’t emit non‑200/non‑3xx codes without a backend. Best static fallback is 200 + noindex.

User Q 5: “Maybe redirect to home or search?”

A: Google treats irrelevant redirects as soft‑404. You lose link equity and create a poor UX.

User Q 6: “404 now good solution for all jobs?”

A: Yes. A true 404 frees crawl budget and removes the page; 410 is faster but optional.

User Q 7 (minimal test):
/vacatures/analytical‑sales‑support.html returns 200 → live.
/jobs/accountant‑boekhouder… returns 404 → gone.

A: Perfect behaviour: live pages stay indexed, expired pages vanish.

7 | Copy‑Paste Mini Configs

7.1 Pure Static 404 (most sites)

{
  "hosting": {
    "public": "public"
  }
}

7.2 Static 410 Work‑around

{
  "hosting": {
    "headers": [{
      "source": "/jobs/**",
      "headers": [{"key":"X-Robots-Tag","value":"noindex"}]
    }],
    "rewrites": [{ "source": "/jobs/**", "destination": "/410.html" }]
  }
}

7.3 True 410 with Function

// firebase.json
{
  "hosting": {
    "rewrites": [
      {"source":"/jobs/**","function":"gone410"}
    ]
  }
}

// functions/index.js
exports.gone410 = (req, res) => {
  res.status(410).send("This job has been permanently removed.");
};

Take‑away: If there’s no direct replacement for an expired job posting, serve a real 404 (or 410) and let Google clean its index. Anything else—soft‑404, irrelevant redirect, or blanket 200—costs crawl budget and confuses both users and search engines.

Comments