Files
thrilltrack-explorer/migration/PHASE_12_PAGES_MIGRATION.md

725 lines
18 KiB
Markdown

# PHASE 12: Next.js 15 App Router Pages Migration
**Status:** ⬜ Not Started
**Estimated Time:** 45-55 hours
**Priority:** CRITICAL
**Depends On:** All previous phases (1-11)
**Blocks:** Phase 13 (Next.js Optimization)
---
## 🎯 Goal
Convert ALL React SPA pages to Next.js 15 App Router pages while replacing Supabase calls with Django services. This is a DUAL migration:
1. **React → Next.js App Router**: Convert pages and routing
2. **Supabase → Django**: Replace all data fetching
**Critical Requirements:**
- Preserve ALL existing URLs
- Maintain exact same UI/UX
- Use Server Components by default
- Mark Client Components with 'use client'
- Sacred Pipeline remains intact
---
## 📋 Next.js App Router Structure
### New Directory Structure
```
app/
├── layout.tsx # Root layout
├── page.tsx # Homepage
├── loading.tsx # Global loading
├── error.tsx # Global error
├── not-found.tsx # 404 page
├── parks/
│ ├── page.tsx # /parks (listing)
│ ├── loading.tsx # Loading state
│ ├── [parkSlug]/
│ │ ├── page.tsx # /parks/[parkSlug]
│ │ ├── loading.tsx
│ │ └── rides/
│ │ └── page.tsx # /parks/[parkSlug]/rides
│ └── owners/
│ └── [ownerSlug]/
│ └── page.tsx # /owners/[ownerSlug]/parks
├── rides/
│ ├── page.tsx # /rides (listing)
│ ├── [rideSlug]/
│ │ ├── page.tsx # /rides/[rideSlug]
│ │ └── reviews/
│ │ └── page.tsx # /rides/[rideSlug]/reviews
│ └── models/
│ └── [modelSlug]/
│ ├── page.tsx # /ride-models/[modelSlug]
│ └── rides/
│ └── page.tsx # /ride-models/[modelSlug]/rides
├── manufacturers/
│ ├── page.tsx # /manufacturers (listing)
│ └── [manufacturerSlug]/
│ ├── page.tsx # /manufacturers/[manufacturerSlug]
│ └── rides/
│ └── page.tsx # /manufacturers/[manufacturerSlug]/rides
├── owners/
│ ├── page.tsx # /owners (listing)
│ └── [ownerSlug]/
│ └── page.tsx # /owners/[ownerSlug]
├── designers/
│ ├── page.tsx # /designers (listing)
│ └── [designerSlug]/
│ └── page.tsx # /designers/[designerSlug]
├── auth/
│ ├── login/
│ │ └── page.tsx
│ ├── register/
│ │ └── page.tsx
│ └── callback/
│ └── page.tsx
├── profile/
│ ├── page.tsx # /profile
│ ├── settings/
│ │ └── page.tsx
│ └── lists/
│ └── page.tsx
├── admin/
│ ├── page.tsx # /admin
│ └── moderation/
│ └── page.tsx # /admin/moderation
├── search/
│ └── page.tsx # /search
└── contact/
└── page.tsx # /contact
```
---
## 📋 Tasks
### Task 12.1: Root Layout & Homepage (6 hours)
#### Create Root Layout
`app/layout.tsx` (Server Component):
```typescript
import { Inter } from 'next/font/google';
import './globals.css';
import { AuthProvider } from '@/components/providers/AuthProvider';
import { Navigation } from '@/components/layout/Navigation';
import { Footer } from '@/components/layout/Footer';
const inter = Inter({ subsets: ['latin'] });
export const metadata = {
title: 'ThrillWiki - Theme Park & Ride Database',
description: 'Comprehensive database of theme parks and rides',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
<AuthProvider>
<Navigation />
<main className="min-h-screen">{children}</main>
<Footer />
</AuthProvider>
</body>
</html>
);
}
```
#### Convert Homepage
`app/page.tsx` (Server Component):
```typescript
import { env } from '@/lib/env';
import { FeaturedParks } from '@/components/home/FeaturedParks';
import { RecentReviews } from '@/components/home/RecentReviews';
import { Stats } from '@/components/home/Stats';
export default async function HomePage() {
// Fetch data server-side
const [parks, stats] = await Promise.all([
fetch(`${env.NEXT_PUBLIC_DJANGO_API_URL}/parks/?featured=true`).then(r => r.json()),
fetch(`${env.NEXT_PUBLIC_DJANGO_API_URL}/stats/`).then(r => r.json()),
]);
return (
<div>
<Hero />
<Stats data={stats} />
<FeaturedParks parks={parks} />
<RecentReviews />
</div>
);
}
```
#### Checklist
- [ ] Create `app/layout.tsx` with root layout
- [ ] Create `app/page.tsx` for homepage
- [ ] Create `app/loading.tsx` for loading state
- [ ] Create `app/error.tsx` for error boundary
- [ ] Create `app/not-found.tsx` for 404
- [ ] Move CSS to `app/globals.css`
- [ ] Test homepage loads
- [ ] Verify navigation works
- [ ] Check loading states
- [ ] Test error boundaries
---
### Task 12.2: Park Pages (8 hours)
#### Park Listing Page
`app/parks/page.tsx` (Server Component):
```typescript
import { env } from '@/lib/env';
import { ParksList } from '@/components/parks/ParksList';
import { ParkFilters } from '@/components/parks/ParkFilters';
export const metadata = {
title: 'Theme Parks - ThrillWiki',
description: 'Browse theme parks from around the world',
};
export default async function ParksPage({
searchParams,
}: {
searchParams: { [key: string]: string | undefined };
}) {
const params = new URLSearchParams(searchParams as any);
const parks = await fetch(
`${env.NEXT_PUBLIC_DJANGO_API_URL}/parks/?${params}`,
{ next: { revalidate: 300 } } // Cache for 5 minutes
).then(r => r.json());
return (
<div>
<h1>Theme Parks</h1>
<ParkFilters /> {/* Client Component */}
<ParksList parks={parks} /> {/* Can be Server Component */}
</div>
);
}
```
#### Park Detail Page
`app/parks/[parkSlug]/page.tsx` (Server Component):
```typescript
import { env } from '@/lib/env';
import { ParkDetail } from '@/components/parks/ParkDetail';
import { notFound } from 'next/navigation';
export async function generateStaticParams() {
// Pre-render top 100 parks
const parks = await fetch(`${env.NEXT_PUBLIC_DJANGO_API_URL}/parks/?page_size=100`)
.then(r => r.json());
return parks.results.map((park: any) => ({
parkSlug: park.slug,
}));
}
export async function generateMetadata({ params }: { params: { parkSlug: string } }) {
const park = await fetch(`${env.NEXT_PUBLIC_DJANGO_API_URL}/parks/${params.parkSlug}/`)
.then(r => r.json());
return {
title: `${park.name} - ThrillWiki`,
description: park.description,
};
}
export default async function ParkDetailPage({
params
}: {
params: { parkSlug: string }
}) {
const park = await fetch(
`${env.NEXT_PUBLIC_DJANGO_API_URL}/parks/${params.parkSlug}/`,
{ next: { revalidate: 3600 } } // Cache for 1 hour
).then(r => r.json())
.catch(() => notFound());
return <ParkDetail park={park} />;
}
```
#### Checklist
- [ ] Create `app/parks/page.tsx`
- [ ] Create `app/parks/[parkSlug]/page.tsx`
- [ ] Create `app/parks/[parkSlug]/rides/page.tsx`
- [ ] Create `app/owners/[ownerSlug]/parks/page.tsx`
- [ ] Create loading.tsx for each route
- [ ] Implement generateStaticParams for ISR
- [ ] Implement generateMetadata for SEO
- [ ] Test all park URLs work
- [ ] Verify filtering works
- [ ] Test pagination
---
### Task 12.3: Ride Pages (8 hours)
#### Ride Listing Page
`app/rides/page.tsx` (Server Component with caching)
#### Ride Detail Page
`app/rides/[rideSlug]/page.tsx` (Server Component)
#### Ride Model Pages
`app/ride-models/[modelSlug]/page.tsx` (Server Component)
#### Checklist
- [ ] Create `app/rides/page.tsx`
- [ ] Create `app/rides/[rideSlug]/page.tsx`
- [ ] Create `app/rides/[rideSlug]/reviews/page.tsx`
- [ ] Create `app/ride-models/[modelSlug]/page.tsx`
- [ ] Create `app/ride-models/[modelSlug]/rides/page.tsx`
- [ ] Create loading states
- [ ] Implement metadata
- [ ] Test all ride URLs
- [ ] Verify reviews work
---
### Task 12.4: Company Pages (8 hours)
Convert manufacturer, owner, and designer pages to App Router.
#### Checklist
- [ ] Create `app/manufacturers/page.tsx`
- [ ] Create `app/manufacturers/[manufacturerSlug]/page.tsx`
- [ ] Create `app/manufacturers/[manufacturerSlug]/rides/page.tsx`
- [ ] Create `app/owners/page.tsx`
- [ ] Create `app/owners/[ownerSlug]/page.tsx`
- [ ] Create `app/designers/page.tsx`
- [ ] Create `app/designers/[designerSlug]/page.tsx`
- [ ] Test all company URLs
- [ ] Verify filtering works
---
### Task 12.5: User Pages (6 hours)
These pages need authentication and user-specific data.
#### Profile Page
`app/profile/page.tsx` (Server Component with auth check):
```typescript
import { redirect } from 'next/navigation';
import { getServerSession } from 'next-auth';
import { ProfileView } from '@/components/profile/ProfileView';
export default async function ProfilePage() {
const session = await getServerSession();
if (!session) {
redirect('/auth/login');
}
const user = await fetch(
`${env.NEXT_PUBLIC_DJANGO_API_URL}/users/me/`,
{
headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store' // Don't cache user-specific data
}
).then(r => r.json());
return <ProfileView user={user} />;
}
```
#### Checklist
- [ ] Create `app/profile/page.tsx`
- [ ] Create `app/profile/settings/page.tsx`
- [ ] Create `app/profile/lists/page.tsx`
- [ ] Implement authentication checks
- [ ] Disable caching for user data
- [ ] Test profile loads
- [ ] Test settings work
- [ ] Test list management
---
### Task 12.6: Admin & Moderation Pages (6 hours)
Admin pages require role checks and real-time updates.
#### Moderation Queue
`app/admin/moderation/page.tsx` (Server Component):
```typescript
import { redirect } from 'next/navigation';
import { getServerSession } from 'next-auth';
import { ModerationQueue } from '@/components/moderation/ModerationQueue';
export default async function ModerationPage() {
const session = await getServerSession();
if (!session || session.user.role !== 'moderator') {
redirect('/');
}
// Fetch submissions server-side
const submissions = await fetch(
`${env.NEXT_PUBLIC_DJANGO_API_URL}/moderation/queue/`,
{
headers: { Authorization: `Bearer ${session.accessToken}` },
cache: 'no-store'
}
).then(r => r.json());
return <ModerationQueue initialSubmissions={submissions} />;
}
```
#### Checklist
- [ ] Create `app/admin/page.tsx`
- [ ] Create `app/admin/moderation/page.tsx`
- [ ] Implement role-based access
- [ ] Add server-side permission checks
- [ ] Test moderator access
- [ ] Test admin dashboard
- [ ] Verify Sacred Pipeline
---
### Task 12.7: Authentication Pages (4 hours)
#### Login Page
`app/auth/login/page.tsx` (Client Component - needs form state):
```typescript
'use client';
import { LoginForm } from '@/components/auth/LoginForm';
import { redirect } from 'next/navigation';
import { useSession } from 'next-auth/react';
export default function LoginPage() {
const { data: session } = useSession();
if (session) {
redirect('/profile');
}
return (
<div>
<h1>Login</h1>
<LoginForm />
</div>
);
}
```
#### Checklist
- [ ] Create `app/auth/login/page.tsx`
- [ ] Create `app/auth/register/page.tsx`
- [ ] Create `app/auth/callback/page.tsx`
- [ ] Create `app/auth/reset-password/page.tsx`
- [ ] Test email/password login
- [ ] Test OAuth login (Google, GitHub)
- [ ] Test registration
- [ ] Test password reset
---
### Task 12.8: Search & Contact Pages (3 hours)
#### Search Page
`app/search/page.tsx` (Client Component - needs interactive search):
```typescript
'use client';
import { useSearchParams } from 'next/navigation';
import { SearchResults } from '@/components/search/SearchResults';
import { SearchFilters } from '@/components/search/SearchFilters';
export default function SearchPage() {
const searchParams = useSearchParams();
const query = searchParams.get('q');
return (
<div>
<h1>Search Results</h1>
<SearchFilters />
<SearchResults query={query} />
</div>
);
}
```
#### Contact Page
`app/contact/page.tsx` (Server Component):
```typescript
import { ContactForm } from '@/components/contact/ContactForm';
export const metadata = {
title: 'Contact Us - ThrillWiki',
};
export default function ContactPage() {
return (
<div>
<h1>Contact Us</h1>
<ContactForm />
</div>
);
}
```
#### Checklist
- [ ] Create `app/search/page.tsx`
- [ ] Create `app/contact/page.tsx`
- [ ] Test search functionality
- [ ] Test contact form submission
- [ ] Verify form validation
---
### Task 12.9: Component Migration (6 hours)
Convert React components to work with Next.js.
#### Server Components (Default)
Components that only display data and don't need interactivity:
- Lists (ParksL, RidesList)
- Detail views (ParkDetail, RideDetail)
- Static content
#### Client Components ('use client')
Components that need interactivity:
- Forms (LoginForm, SubmissionForm)
- Interactive filters
- Modals and dialogs
- Components using useState, useEffect, etc.
#### Checklist
- [ ] Identify all components using Supabase
- [ ] Determine Server vs Client Component
- [ ] Add 'use client' where needed
- [ ] Replace Supabase with service calls
- [ ] Test all components render
- [ ] Verify interactions work
- [ ] Check no hydration errors
---
### Task 12.10: Routing & Navigation (4 hours)
Update all navigation to use Next.js routing.
#### Replace React Router
```typescript
// OLD (React Router)
import { Link, useNavigate } from 'react-router-dom';
// NEW (Next.js)
import Link from 'next/link';
import { useRouter } from 'next/navigation';
```
#### Update All Links
```typescript
// OLD
<Link to="/parks">Parks</Link>
// NEW
<Link href="/parks">Parks</Link>
```
#### Checklist
- [ ] Replace all React Router imports
- [ ] Update all <Link> components
- [ ] Update all navigate() calls to router.push()
- [ ] Update all useParams to use params prop
- [ ] Update all useSearchParams
- [ ] Test navigation works
- [ ] Test browser back button
- [ ] Test deep linking
---
## 🎯 Success Criteria
### Zero React Router Usage
- [ ] No `react-router-dom` imports remain
- [ ] All navigation uses Next.js Link/router
- [ ] No old React pages in `src/pages/` (move to `app/`)
### Zero Supabase Usage
- [ ] `grep -r "supabase\." app/ components/ lib/ --include="*.ts" --include="*.tsx"` returns 0
- [ ] `grep -r "from '@/lib/supabaseClient'"` returns 0
- [ ] `grep -r "from '@supabase/supabase-js'"` returns 0
### All URLs Preserved
- [ ] `/parks` works
- [ ] `/parks/[parkSlug]` works
- [ ] `/parks/[parkSlug]/rides` works
- [ ] `/owners/[ownerSlug]/parks` works
- [ ] `/rides` works
- [ ] `/rides/[rideSlug]` works
- [ ] `/ride-models/[modelSlug]` works
- [ ] `/manufacturers/[manufacturerSlug]` works
- [ ] `/manufacturers/[manufacturerSlug]/rides` works
- [ ] All other URLs work
### All Pages Load
- [ ] Homepage loads (SSR)
- [ ] All park pages load
- [ ] All ride pages load
- [ ] All company pages load
- [ ] Profile page loads (auth check)
- [ ] Settings page loads (auth check)
- [ ] Admin dashboard loads (role check)
- [ ] Moderation queue loads (role check)
- [ ] Search page loads
- [ ] Contact page loads
- [ ] Auth pages load
### All Features Work
- [ ] Can browse entities
- [ ] Can view entity details
- [ ] Can filter/sort
- [ ] Can submit content (creates submission)
- [ ] Can moderate content
- [ ] Can write reviews
- [ ] Can add ride credits
- [ ] Can manage top lists
- [ ] Can upload photos
- [ ] Can search
- [ ] Authentication works
- [ ] Server-side rendering works
- [ ] Client-side navigation works
### Next.js Specific
- [ ] Server Components render on server
- [ ] Client Components work in browser
- [ ] No hydration errors
- [ ] Loading states display
- [ ] Error boundaries catch errors
- [ ] Metadata API generates correct tags
- [ ] ISR/SSR configured correctly
- [ ] Build succeeds without errors
### Sacred Pipeline Intact
- [ ] All entity changes go through submissions
- [ ] Moderation queue receives submissions
- [ ] Approval creates entities/updates
- [ ] Rejection saves reason
- [ ] No pipeline bypasses
---
## 📝 Implementation Strategy
### 1. Start with Entity Pages (Most Critical)
Focus on parks, rides, companies first since they're core functionality.
### 2. Then User Pages
Profile and settings are user-facing and important.
### 3. Then Admin Pages
Moderation queue and admin dashboard.
### 4. Then Misc Pages
Homepage, search, contact, etc.
### 5. Component Sweep
Find any remaining components with Supabase calls.
### 6. Final Search
Run grep commands to find ANY remaining Supabase usage.
---
## 🚨 Critical Rules
### DO NOT
- ❌ Skip any page
- ❌ Leave any `supabase.` calls
- ❌ Assume a page works without testing it
- ❌ Move to Phase 13 until grep returns 0 results
### MUST DO
- ✅ Test EVERY page you touch
- ✅ Verify EVERY interaction works
- ✅ Check console for errors
- ✅ Verify Sacred Pipeline intact
- ✅ Document any issues found
---
## 🔗 Related Documentation
Refer back to previous phases for service usage examples:
- Phase 1: Foundation (BaseService pattern)
- Phase 4: Entity Services (parks, rides, companies)
- Phase 5: Reviews & Social
- Phase 6: Moderation & Admin
- Phase 7: Media & Photos
- Phase 8: Search
- Phase 9: Timeline & History
- Phase 10: Users & Profiles
- Phase 11: Contact & Reports
---
## ⏭️ Next Phase
Once this phase is complete, proceed to [Phase 13: Next.js Optimization](./PHASE_13_NEXTJS_OPTIMIZATION.md)
---
## 🔗 Related Documentation
- [Next.js 15 Migration Guide](./NEXTJS_15_MIGRATION_GUIDE.md)
- [Environment Variables Guide](./ENVIRONMENT_VARIABLES.md)
- [Phase 1: Foundation](./PHASE_01_FOUNDATION.md)
- [Phase 13: Next.js Optimization](./PHASE_13_NEXTJS_OPTIMIZATION.md)
- Next.js App Router: https://nextjs.org/docs/app
- Server Components: https://nextjs.org/docs/app/building-your-application/rendering/server-components
---
**Document Version:** 2.0
**Last Updated:** November 9, 2025
**Changes:** Converted to Next.js 15 App Router migration