ReviewOS

stacks/stx

NON_PAGE_DIRS omits 'emails', so framework email templates are scanned as pages, routed, served and statically built

#1897
Closed glennmichael123 opened this 11 days ago ·
11 days ago

NON_PAGE_DIRS is missing emails, so a framework convention directory of email templates is scanned as pages, routed, served, and statically built to HTML.

What happens

A stacks app's views tree ships an emails/ directory as a first-class convention, alongside the things that genuinely are pages:

$ ls storage/framework/defaults/resources/views/
cart.stx  checkout/  coming-soon.stx  dashboard/  emails/  errors/  index.stx  orders/

Regenerating the route table in a real app puts the email templates in it:

# .stx/routes.ts
+ { pattern: '/emails/welcome', filePath: 'storage/framework/defaults/resources/views/emails/welcome.stx', isDynamic: false, params: [] },

# .stx/route-types.d.ts
+ '/emails/welcome': {  }

So GET /emails/welcome is a page, navigate('/emails/welcome') type-checks, and an SSG build emits it as HTML.

Cause

packages/router/src/page-routes.ts:24:

export const NON_PAGE_DIRS: readonly string[] = ['components', 'layouts', 'partials']

That list gates all three places a non-page directory is excluded — the scan-time skip in file-router.ts:28, and the manifest and route-type filters in codegen.ts:22 and :84. An emails/ directory matches none of them, so it flows through every stage as if it were a section of the site.

This is the failure mode the module was written to prevent

From file-router.ts:25-27, unchanged:

Building blocks, not pages. Skipped at scan time so they are absent from the route table itself — otherwise they get served, statically built to HTML, and listed in sitemap.xml as public URLs (#1866).

That is exactly what emails/ does now. The #1866 fix enumerated three directories and stopped there; emails/ is the same category and was missed.

Why it matters

An email template is not a page. Rendered as one it is a crawlable public URL whose body is the message layout — unsubscribe and preference links, any support or internal addresses in the footer, the reset/verify flow's copy and its link structure, and whatever placeholder scaffolding the template carries. None of that is per-user data, so this is not a data leak; it is unintended public surface, indexed, that no one chose to publish and that reveals the shape of flows an app may not want enumerated.

It also degrades the route types: RouteMap gains entries for URLs that are not navigable destinations.

Fix

Add emails to NON_PAGE_DIRS. Everything downstream — scan skip, manifest filter, type filter — already keys off that one list, so it is a one-line change.

Worth deciding on errors/ in the same pass. It is the other convention directory in that tree, and unlike emails/ there is a real argument for it staying routable (a directly-reachable /errors/404), so it should be a deliberate call rather than another oversight.

packages/router/test/page-routes.test.ts:67-69 asserts the list contents item by item and is the natural place to pin whatever is decided.

Found when a routine route-table regeneration in a production app added /emails/welcome on its own.

11 days ago

Fixed in 8360b8f53f.

emails added to NON_PAGE_DIRS, which gates all three exclusion points — the scan-time skip in file-router.ts and the manifest and route-type filters in codegen.ts — so an email template is no longer routed, served, type-checked as a destination, or written out by an SSG build.

errors/ deliberately stays out of the list: errors/404.stx and errors/500.stx are pages the framework serves on those statuses, and excluding them would break that.

5 tests, including the two boundaries worth having: a file merely named emails.stx is still a page, and admin/emails/invite.stx is skipped at depth the way the other building blocks are.

Sign in to comment on this issue.