Skip to content

Document urlPattern collection property, and add route to catch new pages where missing in the templates#1099

Open
dcardosods wants to merge 6 commits into
emdash-cms:mainfrom
dcardosods:docs-url-pattern
Open

Document urlPattern collection property, and add route to catch new pages where missing in the templates#1099
dcardosods wants to merge 6 commits into
emdash-cms:mainfrom
dcardosods:docs-url-pattern

Conversation

@dcardosods
Copy link
Copy Markdown
Contributor

@dcardosods dcardosods commented May 19, 2026

What does this PR do?

Related discussion: #687 (comment)

  1. Documents urlPattern collection property,
  2. Adds urlPattern to pages collection and /[slug].astro route to avoid preview of pages created using the admin interface going to /pages/{slug} and resulting in 404
  3. Adds urlPattern to the projects collection of portfolio template to match the existing route

I manually tested each template with the following steps:

  1. run template setup
  2. verified seeded pages preview url doesn't result in 404
  3. created new page
  4. verified new page preview url doesn't result in 404

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool:

Screenshots / test output

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 19, 2026

⚠️ No Changeset found

Latest commit: 797ca8a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 19, 2026

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1099

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1099

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@1099

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1099

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1099

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@1099

emdash

npm i https://pkg.pr.new/emdash@1099

create-emdash

npm i https://pkg.pr.new/create-emdash@1099

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1099

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@1099

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@1099

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@1099

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@1099

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@1099

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1099

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1099

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1099

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1099

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1099

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1099

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@1099

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1099

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1099

commit: 797ca8a


When set, this pattern is used in three places:

- **Public routing** — `resolveEmDashPath()` matches incoming request paths against all collection patterns and returns the matching entry.
Copy link
Copy Markdown
Collaborator

@ascorbic ascorbic May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is potentially misleading. It's not used for routing in actual sites by default (they use the Astro router)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this point and rephrase the rest of the section.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates multiple templates and the docs to better support the urlPattern collection property, so admin preview / menu links / public routes align with the intended URL shape (notably for pages at /{slug} and projects at /work/{slug} in the portfolio template).

Changes:

  • Document urlPattern in the seed-files docs and collections concept docs (including how it affects routing, menus, previews, and auto-redirects).
  • Add urlPattern to template seed files (starter, marketing, portfolio; including Cloudflare variants) and add missing src/pages/[slug].astro routes for pages in marketing + portfolio templates.
  • Update template Base.astro layouts to pass richer page context (notably content) into createPublicPageContext.

Reviewed changes

Copilot reviewed 16 out of 20 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
templates/starter/seed/seed.json Add urlPattern for pages to generate root-level page URLs.
templates/starter/emdash-env.d.ts Update generated types (bylines + richer media shape).
templates/starter-cloudflare/seed/seed.json Add urlPattern for pages (Cloudflare starter).
templates/starter-cloudflare/emdash-env.d.ts Update generated types (Cloudflare starter).
templates/portfolio/src/pages/[slug].astro New route to render pages at /{slug} in portfolio template.
templates/portfolio/src/layouts/Base.astro Extend props and public page context wiring (content, canonical, etc.).
templates/portfolio/seed/seed.json Add urlPattern for projects and pages to match template routes.
templates/portfolio/emdash-env.d.ts Update generated types (featured_image shape).
templates/portfolio-cloudflare/src/pages/[slug].astro New route to render pages at /{slug} (Cloudflare portfolio).
templates/portfolio-cloudflare/src/layouts/Base.astro Same Base layout updates for Cloudflare portfolio.
templates/portfolio-cloudflare/seed/seed.json Add urlPattern for projects and pages (Cloudflare portfolio).
templates/portfolio-cloudflare/emdash-env.d.ts Update generated types (Cloudflare portfolio).
templates/marketing/src/pages/[slug].astro New route to render pages at /{slug} in marketing template.
templates/marketing/src/layouts/Base.astro Extend props and public page context wiring (content, canonical, etc.).
templates/marketing/seed/seed.json Add urlPattern for pages to generate root-level page URLs.
templates/marketing-cloudflare/src/pages/[slug].astro New route to render pages at /{slug} (Cloudflare marketing).
templates/marketing-cloudflare/src/layouts/Base.astro Same Base layout updates for Cloudflare marketing.
templates/marketing-cloudflare/seed/seed.json Add urlPattern for pages (Cloudflare marketing).
docs/src/content/docs/themes/seed-files.mdx Document urlPattern in the seed collection property table.
docs/src/content/docs/concepts/collections.mdx Add a new “URL patterns” section describing urlPattern behavior and usage.
Comments suppressed due to low confidence (8)

templates/portfolio/src/layouts/Base.astro:23

  • fullTitle always appends siteTitle to title, but some callers (e.g. pages built with getSeoMeta) may pass a title that already includes the site title. That yields duplicated titles like "X | Studio — Studio". Consider using the same guard used in templates/blog/src/layouts/Base.astro (only append when title doesn’t already include siteTitle).

This issue also appears on line 33 of the same file.

const { title, pageTitle, description, image, canonical, type = "website", content } = Astro.props;
const settings = await getSiteSettings();
const siteTitle = settings?.title || "Studio";
const fullTitle = title ? `${title} — ${siteTitle}` : siteTitle;
const siteDescription = settings?.tagline || "Design & Development";

templates/portfolio/src/layouts/Base.astro:38

  • createPublicPageContext receives canonical straight from props; when callers don’t pass it, EmDashHead won’t emit a canonical link/og:url. If the intent is to always have canonical URLs for these templates (previously this used Astro.url.href), consider defaulting canonical to the current request URL (ideally origin+pathname without query) when no override is provided.
	title: fullTitle,
	pageTitle: pageTitle ?? title ?? siteTitle,
	description: description || siteDescription,
	canonical,
	image,
	content,

templates/marketing/src/layouts/Base.astro:22

  • fullTitle currently appends siteTitle unconditionally when title is set. If a page passes a pre-composed SEO title that already includes siteTitle (e.g. from getSeoMeta), this produces a duplicated <title> like "X | Acme — Acme". Consider guarding against this the same way templates/blog/src/layouts/Base.astro does.

This issue also appears on line 40 of the same file.

const { title, pageTitle, description, image, canonical, content } = Astro.props;
const settings = await getSiteSettings();
const siteTitle = settings?.title || "Acme";
const fullTitle = title ? `${title} — ${siteTitle}` : siteTitle;
const siteDescription =

templates/marketing/src/layouts/Base.astro:50

  • createPublicPageContext now receives canonical from props only. For pages that don’t pass a canonical override, EmDashHead won’t emit canonical/og:url. If canonical tags are desired by default for this template, consider falling back to the current request URL (preferably without query params) when canonical is not provided.
const pageCtx = createPublicPageContext({
	Astro,
	kind: content ? "content" : "custom",
	pageType: "website",
	title: fullTitle,
	pageTitle: pageTitle ?? title ?? siteTitle,
	description: description || siteDescription,
	canonical,
	image,
	content,
	siteName: siteTitle,

templates/portfolio-cloudflare/src/layouts/Base.astro:23

  • Same title composition issue as the non-Cloudflare template: fullTitle appends siteTitle even when the incoming title already includes it (e.g. titles from getSeoMeta), producing duplicated <title> values. Consider adding a guard before appending siteTitle (see templates/blog/src/layouts/Base.astro).

This issue also appears on line 33 of the same file.

const { title, pageTitle, description, image, canonical, type = "website", content } = Astro.props;
const settings = await getSiteSettings();
const siteTitle = settings?.title || "Studio";
const fullTitle = title ? `${title} — ${siteTitle}` : siteTitle;
const siteDescription = settings?.tagline || "Design & Development";

templates/portfolio-cloudflare/src/layouts/Base.astro:38

  • Same canonical handling as the non-Cloudflare template: if canonical isn’t passed in props, EmDashHead won’t emit canonical/og:url. If you want canonical tags by default, consider falling back to the current request URL (ideally origin+pathname) when canonical is not provided.
	title: fullTitle,
	pageTitle: pageTitle ?? title ?? siteTitle,
	description: description || siteDescription,
	canonical,
	image,
	content,

templates/marketing-cloudflare/src/layouts/Base.astro:22

  • Same title composition issue as the non-Cloudflare template: fullTitle appends siteTitle even when title already contains it (common when title comes from getSeoMeta). This can produce duplicated titles like "X | Acme — Acme". Consider guarding against double-appending siteTitle (see templates/blog/src/layouts/Base.astro).

This issue also appears on line 40 of the same file.

const { title, pageTitle, description, image, canonical, content } = Astro.props;
const settings = await getSiteSettings();
const siteTitle = settings?.title || "Acme";
const fullTitle = title ? `${title} — ${siteTitle}` : siteTitle;
const siteDescription =

templates/marketing-cloudflare/src/layouts/Base.astro:50

  • Same canonical handling as the non-Cloudflare template: since canonical is only taken from props, pages that don’t pass it will render without canonical/og:url contributions. Consider defaulting canonical to the current request URL (preferably without query params) when it’s not provided.
const pageCtx = createPublicPageContext({
	Astro,
	kind: content ? "content" : "custom",
	pageType: "website",
	title: fullTitle,
	pageTitle: pageTitle ?? title ?? siteTitle,
	description: description || siteDescription,
	canonical,
	image,
	content,
	siteName: siteTitle,

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +33 to +34
title={seo.title}
description={seo.description}
Comment on lines +33 to +34
title={seo.title}
description={seo.description}
Comment on lines +31 to +32
title={seo.title}
description={seo.description}
Comment on lines +31 to +32
title={seo.title}
description={seo.description}
@github-actions github-actions Bot added the review/needs-rereview Author pushed changes since the last review label May 30, 2026
@ascorbic ascorbic requested a review from Copilot June 5, 2026 11:03
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Jun 5, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 20 changed files in this pull request and generated 8 comments.

pageTitle: pageTitle ?? title ?? siteTitle,
description: description || siteDescription,
canonical: Astro.url.href,
canonical,
pageTitle: pageTitle ?? title ?? siteTitle,
description: description || siteDescription,
canonical: Astro.url.href,
canonical,
pageTitle: pageTitle ?? title ?? siteTitle,
description: description || siteDescription,
canonical: Astro.url.href,
canonical,
pageTitle: pageTitle ?? title ?? siteTitle,
description: description || siteDescription,
canonical: Astro.url.href,
canonical,
Comment on lines +30 to +34
<Base
title={seo.title}
description={seo.description}
content={{ collection: "pages", id: page.data.id, slug }}
>
Comment on lines +30 to +34
<Base
title={seo.title}
description={seo.description}
content={{ collection: "pages", id: page.data.id, slug }}
>
Comment on lines +32 to +36
<Base
title={seo.title}
description={seo.description}
content={{ collection: "pages", id: page.data.id, slug }}
>
Comment on lines +32 to +36
<Base
title={seo.title}
description={seo.description}
content={{ collection: "pages", id: page.data.id, slug }}
>
Copy link
Copy Markdown
Contributor

@emdashbot emdashbot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the right change for the stated problem. It documents urlPattern, seeds the missing urlPattern values in templates whose routes already expect them, and adds root [slug].astro catch-all routes to the marketing and portfolio templates so admin-created pages don’t 404. The approach is sound and consistent with how the starter/blog templates already work.

I checked the full diff, traced the routing on main, and verified what createPublicPageContext / generateBaseSeoContributions do with the canonical field. The implementation is clean, but the Base.astro refactors in the marketing and portfolio templates introduce a regression.

Canonical URL regression
On main, these layouts passed canonical: Astro.url.href to createPublicPageContext, so every page emitted a <link rel="canonical"> and an og:url. The PR removes that default and instead pulls canonical from props. None of the existing pages (contact, pricing, index, about) pass that prop, and neither do the new [slug].astro routes, so canonical links disappear site-wide for those templates. The fix is to keep the default fallback (canonical ?? Astro.url.href) while still allowing the new prop to override it.

No other blockers found. The docs additions are accurate, the urlPattern seeds match the Astro routes, and the new [slug].astro files follow the same patterns as the starter/blog templates.

pageTitle: pageTitle ?? title ?? siteTitle,
description: description || siteDescription,
canonical: Astro.url.href,
canonical,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[needs fixing] Removing the default canonical: Astro.url.href causes a regression: all existing pages (contact.astro, pricing.astro, index.astro) and the new [slug].astro dynamic pages will no longer emit <link rel="canonical"> or og:url meta tags, because none of them pass a canonical prop to Base. Previously every page had a canonical URL derived from Astro.url.href.

Restore the fallback so existing pages keep working while still allowing overrides via the new prop:

Suggested change
canonical,
canonical: canonical ?? Astro.url.href,

pageTitle: pageTitle ?? title ?? siteTitle,
description: description || siteDescription,
canonical: Astro.url.href,
canonical,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[needs fixing] Same regression as the non-Cloudflare marketing template: removing the default Astro.url.href means existing pages and the new [slug].astro route lose canonical links and og:url meta tags because no caller passes the canonical prop.

Restore the fallback:

Suggested change
canonical,
canonical: canonical ?? Astro.url.href,

pageTitle: pageTitle ?? title ?? siteTitle,
description: description || siteDescription,
canonical: Astro.url.href,
canonical,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[needs fixing] Same regression: the old code defaulted canonical to Astro.url.href, so every page emitted a canonical link and og:url. After this change, canonical comes from props and defaults to undefined, which silently drops those tags for existing pages (about.astro, contact.astro, index.astro) and the new [slug].astro route.

Keep the default fallback:

Suggested change
canonical,
canonical: canonical ?? Astro.url.href,

pageTitle: pageTitle ?? title ?? siteTitle,
description: description || siteDescription,
canonical: Astro.url.href,
canonical,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[needs fixing] Same regression as the non-Cloudflare portfolio template: removing the default Astro.url.href drops canonical links and og:url tags for all pages that don't explicitly pass a canonical prop.

Restore the fallback:

Suggested change
canonical,
canonical: canonical ?? Astro.url.href,

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-rereview Author pushed changes since the last review labels Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs area/templates bot:review Trigger an emdashbot code review on this PR cla: signed review/awaiting-author Reviewed; waiting on the author to respond size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants