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: 8faee89

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/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

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/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-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: 8faee89


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}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants