[PR #465] [MERGED] fix: handle URL objects in metadata resolveUrl #592

Closed
opened 2026-05-06 13:08:57 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/465
Author: @benfavre
Created: 3/11/2026
Status: Merged
Merged: 3/12/2026
Merged by: @james-elicx

Base: mainHead: fix/metadata-resolve-url-objects


📝 Commits (6)

  • 77691e8 fix: handle URL objects in metadata resolveUrl
  • b554103 Merge remote-tracking branch 'origin/main' into fix/metadata-resolve-url-objects
  • 7e3cdea fix: update Metadata types and rendering to accept string | URL fields
  • 3e7a0d4 fmt
  • b6369c3 fix: remove dead-code ?? fallbacks and unnecessary test casts
  • ab4eef0 fmt

📊 Changes

2 files changed (+71 additions, -77 deletions)

View changed files

📝 packages/vinext/src/shims/metadata.tsx (+57 -77)
📝 tests/features.test.ts (+14 -0)

📄 Description

Problem

The resolveUrl() function inside MetadataHead only accepts string, but the Next.js Metadata API types several URL fields as string | URL:

  • metadataBase is typed as URL | null (and is already handled correctly as the base)
  • openGraph.url, icon URLs, manifest, alternates, etc. all accept string | URL per Next.js types

When a URL object is passed (which is valid per Next.js API), resolveUrl() calls .startsWith() on it, which crashes:

TypeError: url.startsWith is not a function
    at resolveUrl (packages/vinext/src/shims/metadata.tsx:321)
    at MetadataHead (packages/vinext/src/shims/metadata.tsx:448)

Solution

Add type coercion at the top of resolveUrl():

function resolveUrl(url: string | URL | undefined): string | undefined {
  if (!url) return undefined;
  const s = typeof url === "string" ? url : url instanceof URL ? url.toString() : String(url);
  // ... rest uses `s` instead of `url`
}

Changes

packages/vinext/src/shims/metadata.tsx

  • Changed resolveUrl parameter type from string | undefined to string | URL | undefined
  • Added coercion: URL.toString(), other non-string truthy values → String(url)
  • All subsequent operations use the coerced string s

Reproduction

// In a layout.tsx:
export const metadata = {
  metadataBase: new URL("https://example.com"),
  openGraph: {
    url: "/about",  // resolveUrl receives this as string — works
  },
};

// Or in any page with URL objects in metadata fields:
export const metadata = {
  metadataBase: new URL("https://example.com"),
  // This is valid per Next.js types but crashes vinext:
  alternates: {
    canonical: new URL("https://example.com/page"),
  },
};

Risk

Minimal — purely additive type coercion. String inputs follow the exact same code path as before.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/465 **Author:** [@benfavre](https://github.com/benfavre) **Created:** 3/11/2026 **Status:** ✅ Merged **Merged:** 3/12/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `fix/metadata-resolve-url-objects` --- ### 📝 Commits (6) - [`77691e8`](https://github.com/cloudflare/vinext/commit/77691e86b4265e4955547f36cb32afd7abac6594) fix: handle URL objects in metadata resolveUrl - [`b554103`](https://github.com/cloudflare/vinext/commit/b55410325ee9de7308c436676a72ab27a585be80) Merge remote-tracking branch 'origin/main' into fix/metadata-resolve-url-objects - [`7e3cdea`](https://github.com/cloudflare/vinext/commit/7e3cdeacf84765a2efbbeadf876accbee6ab5ee1) fix: update Metadata types and rendering to accept string | URL fields - [`3e7a0d4`](https://github.com/cloudflare/vinext/commit/3e7a0d45532382983c4a8dbe38030ba17ac06e6a) fmt - [`b6369c3`](https://github.com/cloudflare/vinext/commit/b6369c3f8773ae326979693c8dc2dbf588865b71) fix: remove dead-code ?? fallbacks and unnecessary test casts - [`ab4eef0`](https://github.com/cloudflare/vinext/commit/ab4eef05cb44cbedde9fbb7c13b204cbb1687bdb) fmt ### 📊 Changes **2 files changed** (+71 additions, -77 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/shims/metadata.tsx` (+57 -77) 📝 `tests/features.test.ts` (+14 -0) </details> ### 📄 Description ## Problem The `resolveUrl()` function inside `MetadataHead` only accepts `string`, but the Next.js Metadata API types several URL fields as `string | URL`: - `metadataBase` is typed as `URL | null` (and is already handled correctly as the base) - `openGraph.url`, icon URLs, manifest, alternates, etc. all accept `string | URL` per Next.js types When a `URL` object is passed (which is valid per Next.js API), `resolveUrl()` calls `.startsWith()` on it, which crashes: ``` TypeError: url.startsWith is not a function at resolveUrl (packages/vinext/src/shims/metadata.tsx:321) at MetadataHead (packages/vinext/src/shims/metadata.tsx:448) ``` ## Solution Add type coercion at the top of `resolveUrl()`: ```typescript function resolveUrl(url: string | URL | undefined): string | undefined { if (!url) return undefined; const s = typeof url === "string" ? url : url instanceof URL ? url.toString() : String(url); // ... rest uses `s` instead of `url` } ``` ## Changes **`packages/vinext/src/shims/metadata.tsx`** - Changed `resolveUrl` parameter type from `string | undefined` to `string | URL | undefined` - Added coercion: `URL` → `.toString()`, other non-string truthy values → `String(url)` - All subsequent operations use the coerced string `s` ## Reproduction ```typescript // In a layout.tsx: export const metadata = { metadataBase: new URL("https://example.com"), openGraph: { url: "/about", // resolveUrl receives this as string — works }, }; // Or in any page with URL objects in metadata fields: export const metadata = { metadataBase: new URL("https://example.com"), // This is valid per Next.js types but crashes vinext: alternates: { canonical: new URL("https://example.com/page"), }, }; ``` ## Risk Minimal — purely additive type coercion. String inputs follow the exact same code path as before. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:08:57 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#592
No description provided.