RSS / OG URLs Still Show example.com (Placeholder Domain)

Your RSS items and og:url still point at a starter-template placeholder like example.com or your-domain.com. Here is exactly where it leaks and how to fix every spot in one pass.

You launched a site from an Astro, Next.js, or Hugo starter months ago. It works. Then someone shares an article in Slack and the unfurl shows the right title but the URL preview reads example.com. Or your RSS feed loads fine, yet every item link points at the literal placeholder https://your-domain.com/article. The cause is almost always the same: the starter shipped with a placeholder base URL (example.com, localhost, your-domain.com) and one or more templates still read from it instead of your real domain.

Fastest fix (Astro): set site in astro.config.mjs to your real https:// URL, make your RSS endpoint use site: context.site (not a hardcoded string), rebuild, then re-scrape the URL in the Facebook Sharing Debugger and LinkedIn Post Inspector. For Next.js/Hugo the equivalent is your NEXT_PUBLIC_SITE_URL / baseURL value plus any hardcoded meta templates.

This bug is silent because most page views never touch RSS or OG tags. But aggregators, RSS readers, social unfurls, and AI crawlers all parse them, so wrong links propagate to exactly the surfaces you cannot see in a normal browser visit.

Which bucket are you in

SymptomMost likely sourceFix section
Slack/Discord unfurl shows example.com URLog:url meta template hardcodes placeholderStep 2
RSS item links go to your-domain.com/...RSS endpoint site hardcoded, not context.siteStep 2
Sitemap is correct, RSS/OG are notMultiple sources of truth in the codebaseStep 1 + Step 2
Works locally, broken in production onlySITE_URL env var unset in prod, fallback firesStep 3
OG image fails to load on socialsOG image URL built from placeholder domainStep 2
Fixed everything, preview still wrongThird-party cache not yet refreshedStep 6

Common causes (highest hit rate first)

1. Starter base URL never updated

// astro.config.mjs (Astro starter default)
import { defineConfig } from 'astro/config';

export default defineConfig({
  site: 'https://example.com', // ← never updated
});

In Astro, this single site value feeds the sitemap, canonical URLs, and (when you wire it correctly) the RSS feed. If it still says example.com, everything downstream is wrong.

Spot it:

grep -rn "example\.com\|your-domain\|localhost:[0-9]" src/ astro.config.mjs

Any match outside a comment or a test fixture likely reaches production.

2. OG meta or RSS template hardcodes the placeholder

<meta property="og:url" content={`https://example.com${article.slug}`} />
// src/pages/rss.xml.js — the classic mistake
return rss({
  site: 'https://your-domain.com', // ← hardcoded placeholder
  // ...
});

The template author meant for you to replace these, but they shipped as-is. The fix is not just to swap the string; it is to stop hardcoding it (see Step 2 below).

Spot it: View source on any page and search for og:url — it must show your real domain. Then curl your RSS feed and check the <link> elements.

3. SITE_URL env var missing in production

const url = process.env.SITE_URL || 'https://example.com';

If SITE_URL is set locally (in .env) but never added to the production environment, the fallback fires only in prod. This is the “works on my machine” variant.

Spot it: Open your host’s dashboard (Vercel, Netlify, Cloudflare Pages) and confirm SITE_URL exists in the Production environment, not just Preview/Development.

4. Sitemap is correct but RSS / OG are not

Different files read from different constants. You updated the sitemap path months ago and never touched RSS or OG.

Spot it:

curl -s https://yourdomain.com/sitemap.xml | grep -m1 loc
curl -s https://yourdomain.com/rss.xml | grep -m1 link

If the sitemap shows your domain but RSS shows the placeholder, you have split sources of truth.

5. Static OG image URL has the placeholder

A static share image referenced as https://your-domain.com/og.png. If that host is a placeholder, Facebook and LinkedIn cannot fetch the image and the unfurl falls back to no image.

Shortest path to fix

Step 1: Find every placeholder

grep -rn "example\.com\|your-domain\|localhost:[0-9]\|<YOUR_DOMAIN>" \
  src/ public/ astro.config.mjs next.config.js config.toml 2>/dev/null

List every hit. Each one needs to be updated or, better, rewired to read from a single source.

Step 2: Replace with one canonical source, referenced everywhere

Do not blind find-and-replace. Fix each context so it derives from one value:

  • Astro config (astro.config.mjs): set site: 'https://yourdomain.com' (no trailing slash). This is the single source of truth.

  • RSS endpoint (src/pages/rss.xml.js): pull the base URL from the endpoint context instead of hardcoding it. Per the Astro RSS docs, context.site returns the value you set in astro.config.mjs:

    import rss from '@astrojs/rss';
    
    export function GET(context) \{
      return rss(\{
        title: 'My Site',
        description: 'My feed',
        site: context.site, // ← reads astro.config.mjs `site`
        items: [/* ... */],
      \});
    \}
  • OG / canonical meta: build absolute URLs from Astro.site (the configured value), e.g. new URL(Astro.url.pathname, Astro.site).href. Do not interpolate a literal domain string.

  • Next.js: reference process.env.NEXT_PUBLIC_SITE_URL in metadataBase and your alternates.canonical / openGraph.url, and set that env var in the host.

After this step, no template should contain a hardcoded protocol-plus-domain string.

Step 3: Set the env var in production

# Vercel CLI
vercel env add SITE_URL production
# (paste: https://yourdomain.com)

# Netlify CLI
netlify env:set SITE_URL https://yourdomain.com --context production

Or set it in the host dashboard: Vercel → Project → Settings → Environment Variables; Netlify → Site configuration → Environment variables. Redeploy after adding it — env-var changes do not apply to existing deployments.

Step 4: Rebuild and verify locally

npm run build && npm run preview
# Astro preview serves on http://localhost:4321 by default
curl -s http://localhost:4321/rss.xml | grep -E "<link>|<url>" | head
curl -s http://localhost:4321/ | grep -E "og:url|rel=\"canonical\""

Every URL printed should be your real domain.

Step 5: Verify on production

curl -s https://yourdomain.com/rss.xml | grep -E "<link>" | head
curl -s https://yourdomain.com/ | grep -E "og:url|rel=\"canonical\""

If production still shows the placeholder after a successful deploy, you most likely missed Step 3 (env var) or are reading a cached deploy — hard-refresh and confirm the deploy hash changed.

Step 6: Force-refresh third-party caches

Source fixes do not retroactively update cached previews. Trigger a refresh manually:

  • Facebook / Slack / WhatsApp (all read the same OG cache): paste the URL into the Facebook Sharing Debugger and click Scrape Again. CDN cache can take up to 24 hours; clicking Scrape Again two or three times usually forces it.
  • LinkedIn: use the Post Inspector. LinkedIn’s cache is the stickiest — it can take up to ~7 days to fully clear even after inspection.
  • RSS aggregators (Feedly and similar): they update on their next scheduled poll; there is no manual force button.

Step 7: Add a CI guard so it never regresses

# Post-build step in CI
if grep -rE "example\.com|localhost:[0-9]|your-domain" dist/; then
  echo "ERROR: placeholder URL found in build output"
  exit 1
fi

How to confirm it is fixed

You are done when all three of these show only your real domain:

  1. curl -s https://yourdomain.com/rss.xml — every <link> and <guid> uses your domain.
  2. View source of a live article — og:url and rel="canonical" match the page’s real URL.
  3. The Facebook Sharing Debugger preview, after a fresh scrape, shows your domain and the correct image.

When it is not your fault

After you fix the source, third-party previews can stay wrong for hours (Facebook, up to 24h) or days (LinkedIn, up to ~7 days) because of their caches. Once you have re-scraped in the debuggers and curl confirms your origin is correct, the remaining lag is on their side, not yours.

Easy to misdiagnose as

The trap is assuming “no one reads RSS anymore,” so the wrong feed is harmless. In practice RSS and OG tags feed Slack and Discord unfurls, RSS readers, podcast/newsletter pipelines, and AI assistants that crawl feeds. A placeholder there means wrong links propagate to every surface you cannot see from a normal page visit.

Prevention

  • In Astro, treat astro.config.mjs site as the only base URL and read it via context.site / Astro.site everywhere. Never hardcode a second copy.
  • Make SITE_URL (or NEXT_PUBLIC_SITE_URL) a required env var with no default fallback so a missing value fails the build instead of silently shipping example.com.
  • Keep the Step 7 CI grep permanently; it catches the regression at build time.
  • Audit any new starter template for example.com immediately after cloning, before your first deploy.

FAQ

  • Do search engines penalize a wrong RSS feed? Not as a ranking penalty, but it breaks discovery and external link surfaces, and mismatched canonical/OG URLs can cause duplicate-URL confusion for crawlers.
  • Should canonical, OG, and RSS URLs be absolute? Yes. Always use absolute https:// URLs in canonical, sitemap, RSS <link>, og:url, and JSON-LD. Relative URLs are unreliable across feed readers and scrapers.
  • Why does Slack still show the old URL after I fixed it? Slack reads Facebook’s OG cache. Re-scrape the URL in the Facebook Sharing Debugger; Slack picks up the refreshed value shortly after.
  • My sitemap is right but RSS is wrong — why? Your codebase has two sources of truth. The sitemap reads astro.config.mjs site, but your RSS endpoint hardcodes a string. Switch the RSS endpoint to site: context.site.
  • Is example.com actually safe to use as a placeholder anywhere? Yes — example.com is reserved by RFC 2606 specifically for documentation and examples, which is why starters use it. That is also why it is so easy to forget: it never errors, it just resolves to the wrong place.

Tags: #Domain #DNS #SSL #Troubleshooting #Placeholder URL