Out-of-Stock & Discontinued Products
404 vs 410 vs 301 vs keep-and-noindex — the decision matrix. Seasonal handling and replacement-product redirects done right.
Inventory churn is the e-commerce SEO problem that compounds quietly. A 5,000-SKU catalog turns over 10-30% of its inventory every year. That’s 500-1,500 URLs annually that need an SEO decision: keep, redirect, 404, 410, or noindex. Most retailers default to one rule for all of them, and that single mistake bleeds organic revenue across every season.
TL;DR
- The four options are not interchangeable. 404 (gone, may return), 410 (gone forever), 301 (permanently moved), and 200 + noindex (still useful, don’t rank) signal entirely different things to Google.
- Seasonal products are not discontinued products. Christmas trees in March do not 404; they keep their URL, return their schema availability state as
OutOfStockorLimitedAvailability, and inherit their authority year-over-year. - Replacement-product redirects must be 1:1 in intent. Redirecting 500 discontinued SKUs to your homepage is a soft-404 confession Google will treat accordingly.
The mental model
Out-of-stock and discontinued URLs are like estate-planning decisions for inventory. Some products are just on vacation (out of stock, restocking next month) — they need a held mailbox, not a death certificate. Some are retiring permanently with a clear successor (last year’s iPhone, replaced by this year’s) — they need a forwarding address. Some are shutting down with no successor (a discontinued recipe, a one-off collaboration) — they need a clear “gone, do not redirect” record. Some are ceremonial — kept on the books for compliance or completeness, but no longer active for new business.
The cost of getting this wrong is permanent loss of accumulated authority. A product page that ranked #2 for a high-intent query for three years has accrued backlinks, search history, and entity signals. 404 it on a stockout and you lose all of that overnight; 301 it to a category page and Google often interprets the redirect as a soft-404 anyway and de-indexes both.
Deep dive: the 2026 reality
Status code semantics (Google’s documented interpretation, most recently restated by John Mueller in February 2024 and confirmed in current Search Central docs):
| Status | Meaning to Google | Index behavior | Use when |
|---|---|---|---|
| 200 + InStock schema | Live, available, indexable | Indexed and rankable | Normal product page |
| 200 + OutOfStock schema | Live, unavailable, may return | Stays indexed for ~weeks before potentially being demoted | Temporary stockout, restocking soon |
| 200 + Discontinued schema | Live, permanently unavailable | Soft-404 candidate after weeks | Discontinued but historical/reference value |
| 200 + noindex | Live, do not rank | Removed from index | Variant or admin URL still useful internally |
| 301 | Permanently moved | Source de-indexed, equity transferred | Direct successor exists |
| 302 | Temporarily moved | Source stays indexed | Temporary detour only |
| 404 | Not found, may return | De-indexed in 24-48 hours | Unsure if it’ll come back |
| 410 | Gone permanently | De-indexed faster than 404 | Confirmed permanent removal |
Google has stated for years that 404 and 410 are functionally similar but 410 is processed faster. Empirical tests by Marie Haynes, Glenn Gabe, and Sistrix consistently show 410 URLs dropping from the index in 1-3 days vs 7-14 days for 404. Use 410 when you are certain.
The soft-404 problem on stockouts. Google has been more aggressive since 2022 about classifying out-of-stock product pages as soft-404s when:
- The page returns 200 but visible content says “Out of stock,” “Sold out,” or “No longer available.”
- The schema
availabilityisOutOfStockfor an extended period (Google has not stated the threshold; community evidence suggests 60-90 days). - The page has minimal alternative content (no replacement suggestions, no notify-me form, no related products).
The Helpful Content classifier feeds this signal. A site with hundreds of out-of-stock pages reading “We’re sorry, this product is no longer available” gets demoted at the directory level.
Schema markup for unavailability is now well-defined:
https://schema.org/InStock
https://schema.org/OutOfStock
https://schema.org/LimitedAvailability
https://schema.org/PreOrder
https://schema.org/BackOrder
https://schema.org/Discontinued
https://schema.org/SoldOut
Google’s Merchant Center mirrors these via the availability attribute (in_stock, out_of_stock, preorder, backorder). Misalignment between on-page schema and feed availability gets the SKU disqualified from free listings (Module 63).
Replacement-product redirects require intent matching. A 301 from /products/iphone-14-pro → /products/iphone-15-pro works because the user intent is “current top-tier iPhone.” A 301 from /products/limited-edition-bourbon-2022 → /category/whiskey is treated as a soft-404 because the destination doesn’t satisfy the original query.
Visualizing it
flowchart TD
A[Product going off catalog] --> B{Permanent or temporary?}
B -->|Temporary stockout| C{Restock date known?}
C -->|Yes within 30 days| D[Keep 200, schema OutOfStock, show ETA]
C -->|No or 30+ days| E[Keep 200, schema OutOfStock, notify-me form]
B -->|Permanent| F{Has direct successor?}
F -->|Yes, intent match| G[301 to successor PDP]
F -->|Yes, broader match| H[301 to relevant subcategory]
F -->|No successor, no historical value| I[410 Gone]
F -->|No successor, has backlinks or content value| J[200 + noindex + redirect users]
J --> K[Keep page, show similar products, link to category]
D --> L[Monitor 30 days then re-evaluate]
E --> L
L -->|Still OOS at 90 days| F
Bad vs. expert
The bad approach
The “redirect everything to home” strategy:
# nginx config from a real audit
location /products/ {
if ($request_filename !~ -f) {
return 301 https://example.com/;
}
}
Or worse, the “let the platform decide” strategy where Shopify auto-deletes the URL of a deleted product, returning 404 with no decision logic. Or the “leave 5,000 OOS pages indexed for two years” strategy where every long-tail query lands on “Sorry, this product is no longer available.”
The unified failure mode: no per-SKU decision. The retailer treats inventory churn as an operational problem, not an SEO problem, and the catalog’s authority erodes one stockout at a time.
The expert approach
Build a decision matrix keyed on three inputs: state (temporary/permanent), successor (yes/no), and authority (high/low). Then automate the response.
// Pseudocode for a per-SKU decision engine
type RemovalDecision = '301' | '410' | '404' | '200_oos' | '200_noindex';
interface SkuContext {
stockState: 'in_stock' | 'temp_oos' | 'permanent_oos';
successorSku?: string;
successorIntentMatch?: 'direct' | 'category' | 'none';
monthlyOrganicSessions: number;
externalBacklinks: number;
daysSinceLastSale: number;
isSeasonal: boolean;
}
function decideRemoval(sku: SkuContext): RemovalDecision {
if (sku.isSeasonal) return '200_oos';
if (sku.stockState === 'temp_oos') return '200_oos';
if (sku.stockState === 'permanent_oos') {
if (sku.successorSku && sku.successorIntentMatch === 'direct') return '301';
const hasAuthority = sku.externalBacklinks >= 3 || sku.monthlyOrganicSessions >= 50;
if (hasAuthority && sku.successorIntentMatch === 'category') {
return '301'; // to subcategory, only if intent matches
}
if (hasAuthority) return '200_noindex'; // preserve for users + internal links
return '410'; // no authority, no successor
}
return '200_oos';
}
For a temporary stockout, the page stays live with a clear UX and accurate schema:
<head>
<link rel="canonical" href="https://example.com/products/atelier-nord-red-leather-jacket">
</head>
<body>
<h1>Atelier Nord Red Leather Moto Jacket</h1>
<div class="status status--oos">
<p><strong>Currently out of stock.</strong> Restocking week of June 17.</p>
<form action="/notify" method="post">
<input type="email" name="email" placeholder="Email me when back in stock" required>
<button type="submit">Notify me</button>
</form>
</div>
<!-- Full original content stays — description, specs, reviews -->
<section class="alternatives">
<h2>Similar in stock now</h2>
<!-- 4-8 alternative products -->
</section>
</body>
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Atelier Nord Red Leather Moto Jacket",
"sku": "AN-RLM-001",
"offers": {
"@type": "Offer",
"url": "https://example.com/products/atelier-nord-red-leather-moto-jacket",
"priceCurrency": "USD",
"price": "489.00",
"priceValidUntil": "2026-12-31",
"availability": "https://schema.org/OutOfStock",
"availabilityStarts": "2026-06-17"
}
}
For a discontinued SKU with a direct successor, the redirect is server-level and permanent:
# Direct successor 301s — exact intent match only
location = /products/iphone-14-pro {
return 301 /products/iphone-15-pro;
}
location = /products/iphone-14-pro-max {
return 301 /products/iphone-15-pro-max;
}
# Broader-intent retired products that still drive search demand:
# keep the page live, noindex it, surface alternatives
location = /products/limited-edition-2023-collab {
# Served by the application with noindex header and content
}
For a discontinued SKU with no successor and no authority, 410 Gone:
location ~ ^/products/(retired-sku-1|retired-sku-2|retired-sku-3)$ {
return 410;
}
Seasonal handling is its own pattern. Christmas trees, Halloween costumes, summer dresses, ski gear — these products go OOS predictably and return predictably. The right pattern:
{
"@context": "https://schema.org",
"@type": "Product",
"name": "9-Foot Pre-Lit Fraser Fir Christmas Tree",
"offers": {
"@type": "Offer",
"availability": "https://schema.org/OutOfStock",
"availabilityStarts": "2026-10-01",
"availabilityEnds": "2027-01-15",
"validFrom": "2026-10-01"
}
}
The page stays at the same URL year-round. Authority compounds. The seasonal availability schema tells Google the temporal pattern. Off-season, the page can show a “Back in October” banner plus links to evergreen alternatives.
The replacement-product redirect rule: intent must match. A direct successor (iPhone 14 → iPhone 15) is fine. A category redirect (specific running shoe → all running shoes) is borderline; only do it if the destination ranks for the source’s primary keyword. A homepage redirect is never fine.
Do this today
- Pull a list of all OOS and discontinued SKUs from your platform’s admin export. For Shopify: Products → filter by inventory ≤ 0. For WooCommerce: Products → Stock status. For Magento: catalog grid filter on stock status.
- Cross-reference with GA4 → Engagement → Pages and screens for the last 365 days, exporting URL, sessions, and conversions. Any OOS URL with 50+ sessions/month or recent conversions has authority worth preserving.
- Pull backlink data per OOS URL from Ahrefs, Semrush, or Majestic. URLs with 3+ referring root domains are candidates for 200 + noindex (preserve the link target) rather than 410.
- Apply the decision matrix. Document the choice per SKU in a spreadsheet with columns: URL, decision (301/410/404/200_oos/200_noindex), destination URL if 301, reason.
- Implement at the server level where possible (nginx, Cloudflare Workers, Vercel rewrites) rather than in app code. Server-level redirects are faster, more reliable, and easier to audit.
- Update Product schema. For 200 + OOS pages, set
availabilityto the correct schema.org value and addavailabilityStarts/availabilityEndsif known. Validate with Rich Results Test. - In Google Search Console → Removals, do not use the URL Removal tool for product churn. It’s for emergency takedowns. Let normal status codes propagate.
- Set up a monitoring rule. Any product OOS for 90+ consecutive days gets re-evaluated automatically: confirm it’s truly discontinued, or fix the supply problem. Drift is the enemy.
- For seasonal products, add a year-round content section to the PDP (history of the product, sizing, care instructions). Off-season, the page should be a useful reference, not a tombstone.
- Audit your soft-404 report in GSC monthly. URLs flagged as soft-404 are pages Google decided are dead despite returning 200. Review each and either give them genuine content or send the right status code.
Mark complete
Toggle to remember this module as mastered. Saved to your browser only.
More in this part