feat(routing): move the signed-in app under /dashboard, add a public /ads marketing page - #197
Merged
Merged
Conversation
…/ads page The top level was doing two jobs: /pricing and /about were public, while /projects, /settings and /ads were the signed-in app. That left no room for a marketing page on any path the app had already claimed — /ads in particular, which is the one product here with two sides to sell and nowhere to sell them. Split it: every authenticated resource now lives under /dashboard/**, and the top level belongs entirely to public pages. - app/(app)/* moves wholesale into app/(app)/dashboard/*. The auth gate is the (app) layout, which still wraps all of it, so nothing changes about who can reach what. - next.config redirects every old top-level path to its /dashboard equivalent, so bookmarks, links in already-sent report emails and the URLs in past invoices keep working. 307 rather than 308: none of these were ever indexed (they sat behind a login), so there is no SEO to preserve, and a temporary redirect stays reversible instead of being cached in browsers forever. - /ads is the exception at both ends. The bare path is now the public marketing page, and /ads/house/* is artwork in public/ — redirects run before the filesystem, so a blanket /ads/:path* would have 404'd every house creative. The redirect excludes it explicitly. - The proxy's auth check drops the now-dead /projects, /audits and /settings prefixes. /dashboard is the whole contract. The marketing page sells both sides of the network, and every figure on it is imported from lib/ads/pricing rather than typed in — the publisher's per-click number comes from creditsToPayoutCents, the same function ad_charge_click() mirrors, so what a publisher reads is what actually accrues. Verified against a production build: /ads renders publicly, /ads/house artwork still serves 200, every legacy prefix 307s to its new home, and /dashboard/** 302s signed-out visitors to /login with the new path in ?redirect.
ThreatCrush Security Scan35 finding(s) HIGH/CRITICAL: 5 | MEDIUM: 28 | LOW: 2
Snippets are redacted; ThreatCrush never prints matched credential material. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two changes that are really one: the top level of the site now belongs entirely to public pages, and every signed-in resource lives under
/dashboard/**. That frees/adsto become a public marketing page.Why
The top level was doing two jobs.
/pricingand/aboutwere public;/projects,/settingsand/adswere the signed-in app. There was no room for a marketing page on any path the app had already claimed — and/adsis the one product here with two sides to sell and nowhere to sell them.The move
app/(app)/*moves wholesale intoapp/(app)/dashboard/*. The auth gate is the(app)layout, which still wraps all of it, so nothing changes about who can reach what. All ~360 internal link references,revalidatePathcalls, email links and MCP replies were rewritten to match.next.config.tsredirects every old top-level path to its/dashboardequivalent, so bookmarks, links in already-sent report emails and URLs in past invoices keep working.307 rather than 308 on purpose. None of these paths were ever indexed — they all sat behind a login — so there is no SEO to preserve, and a temporary redirect stays reversible instead of being cached in browsers forever if a top-level path is later wanted for a public page.
Two traps worth flagging
/ads/house/*is artwork inpublic/, not a route. Next runs redirects before the filesystem, so a blanket/ads/:path*would have 404'd every house creative. The redirect excludes it with a lookahead, and the build-verified check below covers it./projects,/auditsand/settings. Those are now dead prefixes;/dashboardis the whole contract.The marketing page
/adssells both sides of the network — advertisers and publishers — and redirects signed-in visitors to/dashboard/ads, since that is where either half is actually actionable.Every figure on it is imported from
lib/ads/pricingrather than typed in, so the page cannot quote a rate the biller has moved off. The publisher's per-click number comes fromcreditsToPayoutCents, the same functionad_charge_click()mirrors, so what a publisher reads there is what actually accrues.Claims were checked against the code rather than assumed: the GitHub PR auto-installer for ad embeds is real (
lib/github/install-ad.ts, wired to/api/ads/slots/[id]/install-embed), the bid-weighted lottery is live on every fill, and the six-hour click dedupe window isCLICK_DEDUPE_WINDOW_MS.Verification
typecheckclean,1451 testspass, production build succeeds. Routing checked with real HTTP against the built server:/ads200— public marketing page/ads/house/promo-rect.webp200— artwork intact/ads/slots307→/dashboard/ads/slots/projects/x307→/dashboard/projects/x/settings/billing,/admin,/promote,/social,/autoblog,/audits/x,/analytics307→/dashboard/.../dashboard/ads(signed out)302→/login?redirect=%2Fdashboard%2Fads/api/ads/serve200— API paths untouchedLogin and signup already defaulted to
/dashboardand honored?redirect, so no change was needed there.Not included
docs/*.mdandprd/still describe the old paths. They read as point-in-time specs rather than live links, so I left them rather than rewriting history — say the word if you'd rather they track the new routes.🤖 Generated with Claude Code