Skip to content

Re-apply tenant lookup refactor with routing fix#949

Merged
rchlfryn merged 14 commits intorevert-926-revert-1.7.0from
fryan/revert-926-revert-1.7.0
Mar 3, 2026
Merged

Re-apply tenant lookup refactor with routing fix#949
rchlfryn merged 14 commits intorevert-926-revert-1.7.0from
fryan/revert-926-revert-1.7.0

Conversation

@rchlfryn
Copy link
Collaborator

@rchlfryn rchlfryn commented Feb 17, 2026

Description

Re-applies the tenant lookup refactor from #897 (which was reverted in #926). This replaces the dynamic Edge Config / API-based tenant resolution with a hardcoded AVALANCHE_CENTERS list as the single source of truth for valid tenant slugs, making middleware synchronous and deterministic.

Also fixes a routing issue where pages were falling through to [...segmentsNotFound] in preview.

Why dynamicParams = true?

The routing works in two phases with different capabilities:

  1. Middleware (edge, no DB access) — decides "is this a valid center slug?" and rewrites the URL
  2. Layout (server, has DB access) — decides "can I actually render this center?"

With dynamicParams = false, there's a rigid third gatekeeper between them: the build-time generateStaticParams() output. That list must perfectly match what middleware sends — if it doesn't, for any reason, routes silently fall through to the catch-all instead of rendering the appropriate not-found page.

With dynamicParams = true, that rigid middle layer is removed. Middleware handles "is this slug valid?" and the layout handles "does this center have data?" Each layer does what it's best positioned to do with the information it has access to. generateStaticParams() still pre-renders centers in the DB — that's a performance optimization, not an access control mechanism.

Why notFound() instead of invariant?

invariant throws a regular error → 500-style error page. notFound() throws a Next.js-specific error → proper 404 page. With dynamicParams = true, reaching the layout with a center that has no DB data is now an expected path (not a bug), so a 404 is the correct response.

Reverts #926

Related Issues

#897

Key Changes

Newly added to fix false 404s

  • dynamicParams = true: Centers in the DB are pre-rendered at build time; others are served on-demand with graceful 404 handling d370ef0
  • invariantnotFound(): Proper 404 pages instead of 500-style crashes when a center lacks DB data c34ee93

How to test

  1. Visit a center that exists in the DB (e.g., nwac.localhost) — should render normally
  2. Visit a center in AVALANCHE_CENTERS but not in the DB — should show the not-found page, not crash
  3. Visit a completely invalid slug — should show the not-found page
  4. Verify the admin tenant selector still works correctly
  5. Test custom domain routing in a preview deployment

@github-actions
Copy link

@rchlfryn rchlfryn changed the base branch from revert-926-revert-1.7.0 to main February 17, 2026 20:15
@rchlfryn rchlfryn marked this pull request as ready for review February 17, 2026 21:01
@rchlfryn rchlfryn requested a review from busbyk February 18, 2026 19:23
@rchlfryn rchlfryn self-assigned this Feb 18, 2026
@rchlfryn rchlfryn changed the base branch from main to revert-926-revert-1.7.0 February 20, 2026 21:11
@rchlfryn rchlfryn changed the title Debug: Revert #926 revert 1.7.0 Re-apply tenant lookup refactor with routing fix Feb 26, 2026
@rchlfryn rchlfryn changed the base branch from revert-926-revert-1.7.0 to main February 26, 2026 06:56
@rchlfryn rchlfryn changed the base branch from main to revert-926-revert-1.7.0 February 26, 2026 06:56
@rchlfryn rchlfryn changed the base branch from revert-926-revert-1.7.0 to main February 26, 2026 06:58
@rchlfryn rchlfryn changed the base branch from main to revert-926-revert-1.7.0 February 26, 2026 06:58
Copy link
Collaborator

@busbyk busbyk left a comment

Choose a reason for hiding this comment

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

Couple error/404 scenarios here:

  • valid tenant slug but tenant does not exist in payload
    • should: 404 and show non-center scoped 404
    • this branch/PR: notFound thrown in layout.tsx RSC but we don't have a custom not-found page one level up so next.js 404 page shown (black page with white 404 text)
  • invalid tenant slug
    • should: 404 and show non-center scoped 404
    • this branch/PR: shows src/app/(frontend)/page.tsx -- I wonder if this is because of the dynamicParams change. But it might just be related to the middleware. I didn't get a chance to test on the target branch/PR.

Custom domain routing for both src/app/(frontend)/[center]/[slug]/page.tsx and src/app/(frontend)/[center]/[...segments]/page.tsx seem to work in this PR so that's good.

Comment on lines +128 to +140
if (!settings) {
return {}
}

const tenant = await resolveTenant(settings.tenant, {
select: {
name: true,
},
})

if (!tenant) {
return {}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Was this throwing an error without these empty object returns?

Copy link
Collaborator Author

@rchlfryn rchlfryn Feb 28, 2026

Choose a reason for hiding this comment

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

Not in practice but it could happen in theory - these are added now that dynamicParams = true

With dynamicParams = false, Next.js 404s before reaching the route for slugs not in generateStaticParams, so generateMetadata was never called for invalid slugs.

With dynamicParams = true, generateMetadata can be called for any slug — including ones without settings/tenant in the DB. The layout's notFound() should prevent this, but Next.js may call generateMetadata in parallel with the layout, so these guards prevent a crash if notFound() hasn't fired yet.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yea good call that the RSC and generateMetadata are called in parallel. Would it make more sense to return some basic error state metadata instead of an empty object like:

{
  title: 'Not found',
  description: '...' // not sure what the description would be - we could omit this
}

It sounds like since the not-found page is nested within this layout, this metadata will be used if that's rendered.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good point! I'll update the fallback to return { title: 'Not found' } — since it's more useful metadata than an empty object, especially since the not-found page will inherit it. I'll leave out description since there's nothing meaningful to say there.

@rchlfryn
Copy link
Collaborator Author

Two 404 issues you noted:

Valid tenant slug but no DB data → shows Next.js default 404 (black page) instead of a custom not-found page

When notFound() is called from the [center] layout, Next.js looks for a not-found.tsx above that layout. The existing [center]/not-found.tsx is a child of the layout, so it can't catch errors thrown from it. There's no (frontend)/not-found.tsx, so Next.js falls back to its default black page.

Fix: Add src/app/(frontend)/not-found.tsx with a styled 404.

Invalid tenant slug → shows src/app/(frontend)/page.tsx (the landing page) instead of a 404

I think this is a middleware gap. When an unknown subdomain like invalid.localhost:3000/ comes in, middleware loops through TENANTS, finds no match, and falls through to passthrough. The request hits Next.js with path /, which matches (frontend)/page.tsx — the landing page. isValidTenantSlug in the layout can't help because middleware never added a slug prefix, so the layout never sees the invalid slug at all.

Fix: Return a 404 from middleware when a subdomain doesn't match any known tenant.

busbyk added 5 commits March 2, 2026 10:56
…gate to root domain even on invalid subdomains or custom domains
…e (frontend) route group is just for folder organization so (frontend)/not-found is at top level
…page.tsx since it's called in parallel with layout.tsx
…etion string change for clarity + adding similar logic for matching custom domain for center not configured for production
@busbyk
Copy link
Collaborator

busbyk commented Mar 2, 2026

@rchlfryn I pushed a few changes. Tried to be descriptive enough in my commit messages but let me know if any of those need further explanation.

I'm happy to merge this into #947 if you think my commits look good.

I'm interested in removing custom domain from the db and just depending on the custom domain configured in src/utilities/tenancy/avalancheCenters.ts so I'd like to open that as a PR off of #947 after this PR merges. Then we can test custom domains in preview and call it good.

@rchlfryn
Copy link
Collaborator Author

rchlfryn commented Mar 3, 2026

@busbyk I think changes look good! I will go ahead and squash and merge this into the revert and you can make a PR off of that.

@rchlfryn rchlfryn merged commit fe18e9b into revert-926-revert-1.7.0 Mar 3, 2026
1 check passed
@rchlfryn rchlfryn deleted the fryan/revert-926-revert-1.7.0 branch March 3, 2026 19:42
@busbyk busbyk mentioned this pull request Mar 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants