refactor(app): move site routes into a (frontend) route group
Payload's admin panel ships its own root layout rendering html/body. Next allows multiple root layouts only when no app/layout.tsx exists, so the site's routes move into their own group. Route groups are invisible to routing: every public URL is unchanged, verified against the build's route table. The metadata file conventions deliberately stay at the app/ root. Moving them into the group renamed /icon.png to /icon-4usi79.png (likewise apple-icon and opengraph-image) and dropped /robots.txt altogether, which would have broken the /icon.png cache-control rule, the outputFileTracingIncludes entry for the share card, and robots.txt. darkThemeSafety reads app/globals.css off disk rather than importing it, so it needed its own path fix — a grep for import specifiers misses it, and it fails as an unrunnable suite rather than a failed assertion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017YmbBhr8s7GusjDE12hrZM
This commit is contained in:
1 parent
eb648f3f76
commit
2437ffce42
21 files changed
+57
-26
No files matched your search
@@ -83,9 +83,21 @@ route to `/cms-api` is sufficient and complete. Do not add the exclusion.
|
||||
## File Structure
|
||||
|
||||
**Moved (Task 2)** — `app/*` → `app/(frontend)/*`, unchanged in content:
|
||||
`layout.tsx`, `page.tsx`, `globals.css`, `robots.ts`, `opengraph-image.tsx`,
|
||||
`icon.png`, `apple-icon.png`, `rankings/`, `admissions/`, `compare/`,
|
||||
`schools/`, `school/`, `api/`, `sitemaps/`, `sitemap.xml/`.
|
||||
`layout.tsx`, `page.tsx`, `globals.css`, `rankings/`, `admissions/`,
|
||||
`compare/`, `schools/`, `school/`, `api/`, `sitemaps/`, `sitemap.xml/`.
|
||||
|
||||
**Deliberately NOT moved — they stay at the `app/` root:** `robots.ts`,
|
||||
`opengraph-image.tsx`, `icon.png`, `apple-icon.png`.
|
||||
|
||||
Next.js metadata file conventions only produce stable root URLs at the `app/`
|
||||
root. Inside a route group they are treated as segment-scoped: verified during
|
||||
execution, moving them into `(frontend)` renamed `/icon.png` to
|
||||
`/icon-4usi79.png`, `/apple-icon.png` to `/apple-icon-4usi79.png`,
|
||||
`/opengraph-image` to `/opengraph-image-4usi79`, and dropped `/robots.txt`
|
||||
entirely. That would have broken the `/icon.png` cache-control rule and the
|
||||
`outputFileTracingIncludes['/opengraph-image']` entry in `next.config.mjs`,
|
||||
and silently removed the site's robots.txt. Route handlers (`sitemap.xml/`,
|
||||
`sitemaps/`, `api/`) are unaffected and move normally.
|
||||
|
||||
**Created:**
|
||||
|
||||
@@ -264,20 +276,25 @@ Write down the passing test count. It must be identical at Step 5.
|
||||
|
||||
```bash
|
||||
cd nextjs-app/app && mkdir -p "(frontend)"
|
||||
git mv layout.tsx page.tsx globals.css robots.ts opengraph-image.tsx \
|
||||
icon.png apple-icon.png \
|
||||
git mv layout.tsx page.tsx globals.css \
|
||||
rankings admissions compare schools school api sitemaps sitemap.xml \
|
||||
"(frontend)/"
|
||||
```
|
||||
|
||||
Verify nothing is left behind — `app/` should now contain only `(frontend)`:
|
||||
`robots.ts`, `opengraph-image.tsx`, `icon.png` and `apple-icon.png` stay at the
|
||||
`app/` root — see "Deliberately NOT moved" in the File Structure section above.
|
||||
Moving them silently changes their URLs and drops robots.txt.
|
||||
|
||||
Verify the split — `app/` should now contain `(frontend)` plus exactly the four
|
||||
metadata conventions:
|
||||
|
||||
```bash
|
||||
cd /Users/tudor/projects/school_compare/nextjs-app && ls app
|
||||
cd /Users/tudor/projects/school_compare/nextjs-app && ls -A app
|
||||
```
|
||||
|
||||
If anything else appears, move it too. `git status --short` is the
|
||||
authoritative check: untracked files do not show in `git diff --stat`.
|
||||
Expected: `(frontend)`, `apple-icon.png`, `icon.png`, `opengraph-image.tsx`,
|
||||
`robots.ts`. If a route directory is still there, move it. `git status --short`
|
||||
is the authoritative check: untracked files do not show in `git diff --stat`.
|
||||
|
||||
- [ ] **Step 3: Update the four test imports**
|
||||
|
||||
@@ -304,13 +321,27 @@ import { GET } from '@/app/(frontend)/api/[...path]/route';
|
||||
|
||||
- [ ] **Step 4: Find any other references to the old paths**
|
||||
|
||||
Two searches, because import specifiers are not the only way a file names a
|
||||
path. Quote the `--include` globs or zsh expands them.
|
||||
|
||||
```bash
|
||||
cd /Users/tudor/projects/school_compare && grep -rn "app/layout\|app/page\|@/app/" \
|
||||
--include=*.ts --include=*.tsx --include=*.js --include=*.mjs \
|
||||
cd /Users/tudor/projects/school_compare
|
||||
# 1. Import specifiers
|
||||
grep -rn "app/layout\|app/page\|@/app/" \
|
||||
--include="*.ts" --include="*.tsx" --include="*.js" --include="*.mjs" \
|
||||
nextjs-app --exclude-dir=node_modules --exclude-dir=.next | grep -v "app/(frontend)"
|
||||
|
||||
# 2. Filesystem paths — readFileSync/path.join targets, which search 1 misses.
|
||||
# __tests__/components/darkThemeSafety.test.ts reads app/globals.css this way
|
||||
# and fails to *run* when the path is stale, so it shows as a failed suite
|
||||
# rather than a failed assertion.
|
||||
grep -rn "'app'" nextjs-app/__tests__ --include="*.ts" --include="*.tsx" \
|
||||
| grep -v "(frontend)"
|
||||
```
|
||||
|
||||
Expected: no output. Fix anything that appears.
|
||||
Expected: no output from either. Fix anything that appears — note that
|
||||
`renderSchoolDetail.tsx` names `app/layout.tsx` in a comment, which should be
|
||||
updated for accuracy even though nothing breaks.
|
||||
|
||||
- [ ] **Step 5: Verify tests, types and build**
|
||||
|
||||
@@ -595,7 +626,7 @@ proxy's catch-all."
|
||||
indexed and must not be reachable through search results.
|
||||
|
||||
**Files:**
|
||||
- Modify: `nextjs-app/app/(frontend)/robots.ts`
|
||||
- Modify: `nextjs-app/app/robots.ts`
|
||||
- Modify: `nextjs-app/next.config.mjs`
|
||||
- Modify: `nextjs-app/__tests__/app/nextConfig.test.ts`
|
||||
- Create: `nextjs-app/__tests__/app/robots.test.ts`
|
||||
@@ -609,7 +640,7 @@ indexed and must not be reachable through search results.
|
||||
Create `nextjs-app/__tests__/app/robots.test.ts`:
|
||||
|
||||
```ts
|
||||
import robots from '@/app/(frontend)/robots';
|
||||
import robots from '@/app/robots';
|
||||
|
||||
describe('robots.txt', () => {
|
||||
it('disallows the admin panel and the CMS API', () => {
|
||||
@@ -651,7 +682,7 @@ rule exists.
|
||||
|
||||
- [ ] **Step 3: Add the robots disallow entries**
|
||||
|
||||
In `nextjs-app/app/(frontend)/robots.ts`, change the `disallow` array to:
|
||||
In `nextjs-app/app/robots.ts`, change the `disallow` array to:
|
||||
|
||||
```ts
|
||||
disallow: ['/api/', '/_next/', '/admin/', '/cms-api/'],
|
||||
@@ -1527,7 +1558,7 @@ without a webhook."
|
||||
- Create: `nextjs-app/components/blog/CalloutBlock.tsx` + `.module.css`
|
||||
- Create: `nextjs-app/app/(frontend)/blog/rss.xml/route.ts`
|
||||
- Create: `nextjs-app/app/(frontend)/content-sitemap.xml/route.ts`
|
||||
- Modify: `nextjs-app/app/(frontend)/robots.ts`
|
||||
- Modify: `nextjs-app/app/robots.ts`
|
||||
- Modify: `nextjs-app/lib/jsonld.ts`
|
||||
- Create: `nextjs-app/__tests__/app/blogMetadata.test.ts`
|
||||
- Modify: `e2e/tests/journeys.spec.ts`
|
||||
@@ -2123,7 +2154,7 @@ ${urls.map(({ loc, lastmod }) =>
|
||||
|
||||
- [ ] **Step 9: List both sitemaps in robots.txt**
|
||||
|
||||
In `nextjs-app/app/(frontend)/robots.ts`, change the `sitemap` key to:
|
||||
In `nextjs-app/app/robots.ts`, change the `sitemap` key to:
|
||||
|
||||
```ts
|
||||
sitemap: [absoluteUrl('/sitemap.xml'), absoluteUrl('/content-sitemap.xml')],
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// environment provides — under jsdom this suite fails on import, not on an
|
||||
// assertion.
|
||||
import { NextRequest } from 'next/server';
|
||||
import { GET } from '@/app/api/[...path]/route';
|
||||
import { GET } from '@/app/(frontend)/api/[...path]/route';
|
||||
|
||||
function request(path: string) {
|
||||
return new NextRequest(`http://localhost:3000/api/${path}`);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { metadata as homeMetadata } from '@/app/page';
|
||||
import { metadata as rankingsMetadata } from '@/app/rankings/page';
|
||||
import { metadata as admissionsMetadata } from '@/app/admissions/page';
|
||||
import { generateMetadata as compareMetadata } from '@/app/compare/page';
|
||||
import { metadata as homeMetadata } from '@/app/(frontend)/page';
|
||||
import { metadata as rankingsMetadata } from '@/app/(frontend)/rankings/page';
|
||||
import { metadata as admissionsMetadata } from '@/app/(frontend)/admissions/page';
|
||||
import { generateMetadata as compareMetadata } from '@/app/(frontend)/compare/page';
|
||||
|
||||
describe('canonical URLs', () => {
|
||||
it('the homepage canonicalises to the bare root', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { generateMetadata as placeMeta } from '@/app/schools/[place]/page';
|
||||
import { generateMetadata as placeMeta } from '@/app/(frontend)/schools/[place]/page';
|
||||
|
||||
jest.mock('@/lib/places', () => ({
|
||||
...jest.requireActual('@/lib/places'),
|
||||
|
||||
@@ -109,7 +109,7 @@ describe('dark-theme safety', () => {
|
||||
* simply missed.
|
||||
*/
|
||||
describe('third-party surfaces under themed text', () => {
|
||||
const GLOBALS = path.join(__dirname, '..', '..', 'app', 'globals.css');
|
||||
const GLOBALS = path.join(__dirname, '..', '..', 'app', '(frontend)', 'globals.css');
|
||||
|
||||
/** Leaflet surfaces our own code writes token-coloured text onto. */
|
||||
const LEAFLET_POPUP_SURFACES = [
|
||||
@@ -171,7 +171,7 @@ describe('third-party surfaces under themed text', () => {
|
||||
*/
|
||||
describe('destination tokens', () => {
|
||||
const css = fs.readFileSync(
|
||||
path.join(__dirname, '..', '..', 'app', 'globals.css'), 'utf8');
|
||||
path.join(__dirname, '..', '..', 'app', '(frontend)', 'globals.css'), 'utf8');
|
||||
|
||||
const TOKENS = [
|
||||
'--dest-sixthform', '--dest-sfcollege', '--dest-fecollege',
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
import { nationalAveragesFixture } from './schoolFixtures';
|
||||
|
||||
// The shell calls useComparison(), which throws outside the provider. In the
|
||||
// app this wrapper comes from app/layout.tsx.
|
||||
// app this wrapper comes from app/(frontend)/layout.tsx.
|
||||
function withProviders(ui: ReactNode) {
|
||||
return <ComparisonProvider>{ui}</ComparisonProvider>;
|
||||
}
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user