fix(core): inject favicon link with correct MIME type from site settings#847
fix(core): inject favicon link with correct MIME type from site settings#847
Conversation
Resolves #831. The user-configured site favicon was never emitted into the public site's <head> by core. Per-template Base.astro layouts each rendered their own <link rel="icon">, but pre-#448 templates lacked the line entirely, and even current templates dropped the type attribute. SVG favicons therefore failed to render in Chromium browsers, which require type="image/svg+xml" when the URL has no .svg extension. EmDashHead now emits a <link rel="icon"> with the correct type attribute sourced from the stored media's MIME type. The work lives in a new renderSiteIdentity helper rather than the plugin contribution pipeline because that pipeline's isSafeHref allowlist rejects same-origin paths (correct for sandboxed plugin contributions, but blocks our own /_emdash/api/media/file/... favicon URLs). MediaReference now carries url, contentType, width, and height when resolved via resolveMediaReference, so callers can emit correct head tags without a second round-trip to the media table. Templates that already render their own favicon link continue to work; browsers tolerate the duplicate, and a follow-up cleanup can drop the per-template line. Query-count neutral: getSiteSettings() is request-cached and the standard Base.astro pattern already calls it before EmDashHead runs.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-perf-coordinator | 2eb7071 | Apr 30 2026, 01:02 PM |
🦋 Changeset detectedLatest commit: 2eb7071 The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | 2eb7071 | Apr 30 2026, 01:03 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-i18n | 2eb7071 | Apr 30 2026, 01:02 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | 2eb7071 | Apr 30 2026, 01:02 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | 2eb7071 | Apr 30 2026, 01:03 PM |
|
/review |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/blocks
@emdash-cms/cloudflare
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
There was a problem hiding this comment.
Pull request overview
Centralizes public-site favicon injection in core (EmDashHead) and ensures the emitted <link rel="icon"> includes the correct MIME type (notably image/svg+xml) based on the stored media row, fixing Chromium SVG favicon rendering for extension-less URLs.
Changes:
- Add
renderSiteIdentity()to render first-party site-identity<head>tags (currently favicon), with HTML-attribute escaping. - Extend resolved
MediaReferencevalues to includecontentType,width, andheightalongsideurl(no stored-shape change). - Update
EmDashHead.astroto load full site settings and inject the favicon link; add unit tests + changeset.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/components/EmDashHead.astro | Switches to getSiteSettings() and injects renderSiteIdentity() output into <head>. |
| packages/core/src/page/site-identity.ts | New helper to render favicon <link> with optional type and proper attribute escaping. |
| packages/core/src/settings/index.ts | Enhances media reference resolution to include MIME type + intrinsic dimensions. |
| packages/core/src/settings/types.ts | Documents and extends MediaReference with resolved-only fields (url, contentType, width, height). |
| packages/core/tests/unit/page/site-identity.test.ts | Adds focused unit coverage for favicon rendering behavior and escaping. |
| .changeset/fix-favicon-injection.md | Patch changeset documenting the bugfix and the resolved MediaReference additions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
I'll honestly assess. The bug fix is correct, scoped, well-tested, and the latent risk is documented in code. I don't have a HIGH or MEDIUM bug. The "post LOW for defense-in-depth" is exactly the kind of "while I'm here" comment that the skill says NOT to manufacture: "If you found nothing, say 'No bugs found' and stop. Don't manufacture issues to seem thorough." The PR description explicitly addresses everything I'd flag. Time to call it. LGTM! |
Removes the redundant <link rel="icon"> from all 16 bundled Base.astro layouts (templates, demos, fixtures, infra) and drops siteFavicon from the matching site-identity helpers. Core's EmDashHead now emits the favicon link via renderSiteIdentity() with the correct type attribute, so per-template handling would just produce a duplicate tag. Existing user sites that still emit their own <link rel="icon"> continue to work because browsers tolerate the duplicate. Updated blog-site-identity and starter-site-identity unit tests to reflect the new helper return shape.
|
Addressing review feedback in 2eb7071:
|
What does this PR do?
User-configured site favicons were never injected into the public site's
<head>by core. Per-templateBase.astrolayouts each rendered their own<link rel="icon">, but pre-#448 templates lacked the line entirely, and even current templates dropped thetypeattribute. SVG favicons therefore failed to render in Chromium browsers, which requiretype="image/svg+xml"when the URL has no.svgextension.This PR centralizes favicon injection in
EmDashHeadand emits the correct MIME type from the stored media row.Closes #831
Approach
A new
renderSiteIdentity()helper inpackages/core/src/page/site-identity.tsemits a<link rel="icon">tag with the optionaltypeattribute. It deliberately lives outside the plugin contribution pipeline (page/metadata.ts) because that pipeline'sisSafeHrefallowlist rejects same-origin paths like/_emdash/api/media/file/.... That restriction is correct for sandboxed plugin contributions but blocks our own first-party favicon URLs.MediaReferencenow carriesurl,contentType,width, andheightwhen resolved viaresolveMediaReference, so callers can emit correct head tags without a second round-trip to the media table. The stored shape is unchanged: only the in-memory resolved value picks up the extra fields. Zod schemas at the REST/MCP boundary still define just{ mediaId, alt? }and rely on default strip-mode parsing to discard the resolved fields if a client posts them back. A docstring onMediaReferencecalls this out so a future contributor doesn't accidentally relax the schema and start writing the resolved fields back to storage.EmDashHead.astronow callsgetSiteSettings()instead ofgetSiteSetting("seo")so it can pick upfaviconalongside the SEO settings. Both helpers are request-cached and worker-cached; the standardBase.astropattern in every shipped template already callsgetSiteSettings()first, so this is a free read in practice.Backwards compatibility
Templates that already render their own favicon link continue to work. Browsers tolerate the duplicate. A follow-up cleanup can drop the per-template line once this has shipped, but it does not need to be coordinated.
Query-count impact
Zero net delta from this change.
getSiteSettings()is request-cached;Base.astroalready calls it beforeEmDashHeadrenders. Verified by runningpnpm query-countswith and without the change applied: identical numbers either way. The repository's snapshot is currently stale relative to actual behaviour onmain(every public route is +1 to +6 queries above the recorded values), but that pre-existing drift is unrelated to this PR.Type of change
Checklist
pnpm typecheckpassespnpm lintpasses (0 diagnostics)pnpm testpasses (3053 / 3053 in core)pnpm formathas been runtests/unit/page/site-identity.test.ts)messages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain.AI-generated code disclosure
Screenshots / test output
The 7 new tests cover:
MediaReferencewithout resolvedurlis a no-op (handles deleted media row gracefully)urlandcontentTyperenders the correct tagtype="image/svg+xml"(the Favicon functionality fails against vectorized images #831 bug)contentTypefalls back to a typeless<link>tag (legacy stored media)urlandcontentTypeis HTML-attribute-escaped viaescapeHtmlAttr