mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 01:11:12 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
547
migration/PHASE_13_NEXTJS_OPTIMIZATION.md
Normal file
547
migration/PHASE_13_NEXTJS_OPTIMIZATION.md
Normal file
@@ -0,0 +1,547 @@
|
||||
# PHASE 13: Next.js 15 Optimization
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Estimated Time:** 15-20 hours
|
||||
**Priority:** HIGH
|
||||
**Depends On:** Phase 12 (Next.js Pages Migration)
|
||||
**Blocks:** Phase 14 (Cleanup & Testing)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Phase Goal
|
||||
|
||||
Optimize the Next.js 15 application for production with Turbopack, implement advanced features, and ensure peak performance.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
- [ ] Phase 12 complete (all pages migrated to Next.js App Router)
|
||||
- [ ] All services converted from Supabase to Django API
|
||||
- [ ] Sacred Pipeline working in Next.js
|
||||
- [ ] Bun is configured as package manager
|
||||
- [ ] Environment variables properly configured
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.1: Turbopack Configuration (3 hours)
|
||||
|
||||
### Configure next.config.js for Turbopack
|
||||
|
||||
Create/update `next.config.js`:
|
||||
|
||||
```javascript
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Enable Turbopack for development
|
||||
experimental: {
|
||||
turbo: {
|
||||
rules: {
|
||||
'*.svg': {
|
||||
loaders: ['@svgr/webpack'],
|
||||
as: '*.js',
|
||||
},
|
||||
},
|
||||
resolveAlias: {
|
||||
'@': './src',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Image optimization
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'imagedelivery.net', // CloudFlare Images
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: process.env.NEXT_PUBLIC_DJANGO_API_URL?.replace('https://', '') || 'api.thrillwiki.com',
|
||||
},
|
||||
],
|
||||
formats: ['image/avif', 'image/webp'],
|
||||
},
|
||||
|
||||
// Environment variables
|
||||
env: {
|
||||
NEXT_PUBLIC_DJANGO_API_URL: process.env.NEXT_PUBLIC_DJANGO_API_URL,
|
||||
},
|
||||
|
||||
// Production optimizations
|
||||
compress: true,
|
||||
poweredByHeader: false,
|
||||
reactStrictMode: true,
|
||||
|
||||
// Output configuration
|
||||
output: 'standalone', // For Docker deployment
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
```
|
||||
|
||||
### Checklist
|
||||
- [ ] `next.config.js` created/updated
|
||||
- [ ] Turbopack rules configured
|
||||
- [ ] Image optimization configured for CloudFlare
|
||||
- [ ] Environment variables properly referenced
|
||||
- [ ] Production flags enabled
|
||||
- [ ] Development server uses Turbopack
|
||||
- [ ] Hot reload works correctly
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.2: Server vs Client Component Optimization (4 hours)
|
||||
|
||||
### Identify Component Types
|
||||
|
||||
**Server Components (Default):**
|
||||
- Data fetching pages
|
||||
- Static content
|
||||
- SEO-critical pages
|
||||
- No user interaction
|
||||
|
||||
**Client Components (Need 'use client'):**
|
||||
- Forms with state
|
||||
- Interactive UI elements
|
||||
- Hooks (useState, useEffect, etc.)
|
||||
- Event handlers
|
||||
- Context providers
|
||||
|
||||
### Optimization Checklist
|
||||
|
||||
#### Server Components
|
||||
- [ ] All data-fetching pages are Server Components
|
||||
- [ ] Park listing page is Server Component
|
||||
- [ ] Ride listing page is Server Component
|
||||
- [ ] Detail pages use Server Components for data
|
||||
- [ ] SEO metadata generated server-side
|
||||
|
||||
#### Client Components
|
||||
- [ ] Form components marked with 'use client'
|
||||
- [ ] Interactive UI (modals, dropdowns) marked
|
||||
- [ ] Authentication components marked
|
||||
- [ ] Submission forms marked
|
||||
- [ ] No unnecessary 'use client' directives
|
||||
|
||||
#### Component Structure
|
||||
```typescript
|
||||
// Good: Server Component with Client Components inside
|
||||
// app/parks/page.tsx
|
||||
export default async function ParksPage() {
|
||||
const parks = await fetch(API_URL).then(r => r.json());
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Parks</h1>
|
||||
<ParkFilters /> {/* Client Component */}
|
||||
<ParksList parks={parks} /> {/* Can be Server Component */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Client Component for interactivity
|
||||
// components/ParkFilters.tsx
|
||||
'use client';
|
||||
export function ParkFilters() {
|
||||
const [filters, setFilters] = useState({});
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.3: Data Fetching Optimization (3 hours)
|
||||
|
||||
### Implement Caching Strategies
|
||||
|
||||
```typescript
|
||||
// app/parks/[parkSlug]/page.tsx
|
||||
export async function generateStaticParams() {
|
||||
// Pre-render top 100 parks at build time
|
||||
const parks = await fetch(`${API_URL}/parks/?page_size=100`);
|
||||
return parks.results.map((park) => ({
|
||||
parkSlug: park.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export default async function ParkDetailPage({
|
||||
params
|
||||
}: {
|
||||
params: { parkSlug: string }
|
||||
}) {
|
||||
// Cache for 1 hour, revalidate in background
|
||||
const park = await fetch(`${API_URL}/parks/${params.parkSlug}/`, {
|
||||
next: { revalidate: 3600 }
|
||||
}).then(r => r.json());
|
||||
|
||||
return <ParkDetail park={park} />;
|
||||
}
|
||||
```
|
||||
|
||||
### Caching Strategy Checklist
|
||||
- [ ] Static pages use ISR (Incremental Static Regeneration)
|
||||
- [ ] Dynamic pages have appropriate revalidation times
|
||||
- [ ] Listing pages cache for 5-10 minutes
|
||||
- [ ] Detail pages cache for 1 hour
|
||||
- [ ] User-specific pages are not cached
|
||||
- [ ] Search results are not cached
|
||||
|
||||
### Parallel Data Fetching
|
||||
```typescript
|
||||
// Fetch multiple resources in parallel
|
||||
export default async function RideDetailPage({ params }) {
|
||||
const [ride, reviews, photos] = await Promise.all([
|
||||
fetch(`${API_URL}/rides/${params.rideSlug}/`),
|
||||
fetch(`${API_URL}/rides/${params.rideSlug}/reviews/`),
|
||||
fetch(`${API_URL}/rides/${params.rideSlug}/photos/`),
|
||||
]);
|
||||
|
||||
return <RideDetail ride={ride} reviews={reviews} photos={photos} />;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] Parallel fetching implemented where possible
|
||||
- [ ] No waterfall requests
|
||||
- [ ] Loading states for slow requests
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.4: Loading States & Suspense (2 hours)
|
||||
|
||||
### Create Loading Components
|
||||
|
||||
```typescript
|
||||
// app/parks/loading.tsx
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="h-8 bg-gray-200 rounded animate-pulse" />
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<div key={i} className="h-48 bg-gray-200 rounded animate-pulse" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Implement Streaming
|
||||
|
||||
```typescript
|
||||
// app/parks/[parkSlug]/page.tsx
|
||||
import { Suspense } from 'react';
|
||||
|
||||
export default async function ParkPage({ params }) {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback={<ParkHeaderSkeleton />}>
|
||||
<ParkHeader slug={params.parkSlug} />
|
||||
</Suspense>
|
||||
|
||||
<Suspense fallback={<RidesListSkeleton />}>
|
||||
<RidesList parkSlug={params.parkSlug} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Checklist
|
||||
- [ ] Loading.tsx for each major route
|
||||
- [ ] Suspense boundaries for slow components
|
||||
- [ ] Skeleton screens match final UI
|
||||
- [ ] Streaming SSR enabled
|
||||
- [ ] No flash of loading state
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.5: Metadata API for SEO (2 hours)
|
||||
|
||||
### Implement Dynamic Metadata
|
||||
|
||||
```typescript
|
||||
// app/parks/[parkSlug]/page.tsx
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export async function generateMetadata({
|
||||
params
|
||||
}: {
|
||||
params: { parkSlug: string }
|
||||
}): Promise<Metadata> {
|
||||
const park = await fetch(`${API_URL}/parks/${params.parkSlug}/`)
|
||||
.then(r => r.json());
|
||||
|
||||
return {
|
||||
title: `${park.name} - ThrillWiki`,
|
||||
description: park.description || `Discover ${park.name}, a ${park.park_type} featuring exciting rides and attractions.`,
|
||||
openGraph: {
|
||||
title: park.name,
|
||||
description: park.description,
|
||||
images: [
|
||||
{
|
||||
url: park.banner_image_url || '/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: park.name,
|
||||
},
|
||||
],
|
||||
type: 'website',
|
||||
siteName: 'ThrillWiki',
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: park.name,
|
||||
description: park.description,
|
||||
images: [park.banner_image_url || '/og-image.png'],
|
||||
},
|
||||
alternates: {
|
||||
canonical: `https://thrillwiki.com/parks/${park.slug}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### SEO Checklist
|
||||
- [ ] All pages have unique titles
|
||||
- [ ] All pages have meta descriptions
|
||||
- [ ] OpenGraph tags for social sharing
|
||||
- [ ] Twitter Card tags
|
||||
- [ ] Canonical URLs
|
||||
- [ ] Structured data (JSON-LD)
|
||||
- [ ] Robots.txt configured
|
||||
- [ ] Sitemap.xml generated
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.6: Performance Optimization (3 hours)
|
||||
|
||||
### Bundle Analysis
|
||||
|
||||
```bash
|
||||
# Install bundle analyzer
|
||||
bun add @next/bundle-analyzer
|
||||
|
||||
# Update next.config.js
|
||||
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
});
|
||||
|
||||
module.exports = withBundleAnalyzer(nextConfig);
|
||||
|
||||
# Run analysis
|
||||
ANALYZE=true bun run build
|
||||
```
|
||||
|
||||
### Code Splitting
|
||||
|
||||
```typescript
|
||||
// Use dynamic imports for heavy components
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const HeavyChart = dynamic(() => import('./HeavyChart'), {
|
||||
loading: () => <p>Loading chart...</p>,
|
||||
ssr: false, // Don't render on server
|
||||
});
|
||||
```
|
||||
|
||||
### Performance Checklist
|
||||
- [ ] Bundle size < 500KB initial load
|
||||
- [ ] Code splitting for large dependencies
|
||||
- [ ] Images use next/image
|
||||
- [ ] Fonts optimized with next/font
|
||||
- [ ] CSS modules for component styles
|
||||
- [ ] No unused dependencies
|
||||
- [ ] Tree shaking working
|
||||
|
||||
### Core Web Vitals Targets
|
||||
- [ ] LCP (Largest Contentful Paint) < 2.5s
|
||||
- [ ] FID (First Input Delay) < 100ms
|
||||
- [ ] CLS (Cumulative Layout Shift) < 0.1
|
||||
- [ ] TTFB (Time to First Byte) < 600ms
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.7: Environment Variables & Security (1 hour)
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
Create `.env.local`:
|
||||
```bash
|
||||
# Django API (required for build)
|
||||
NEXT_PUBLIC_DJANGO_API_URL=https://api.thrillwiki.com
|
||||
|
||||
# CloudFlare (public)
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID=xxx
|
||||
|
||||
# Server-only secrets (no NEXT_PUBLIC_ prefix)
|
||||
CLOUDFLARE_API_TOKEN=xxx
|
||||
DJANGO_SECRET_KEY=xxx
|
||||
```
|
||||
|
||||
### Security Checklist
|
||||
- [ ] No secrets in client-side code
|
||||
- [ ] Server-only vars don't have NEXT_PUBLIC_ prefix
|
||||
- [ ] .env.local in .gitignore
|
||||
- [ ] .env.example documented
|
||||
- [ ] Environment validation on startup
|
||||
- [ ] Sensitive data not logged
|
||||
|
||||
### Runtime Validation
|
||||
|
||||
```typescript
|
||||
// lib/env.ts
|
||||
import { z } from 'zod';
|
||||
|
||||
const envSchema = z.object({
|
||||
NEXT_PUBLIC_DJANGO_API_URL: z.string().url(),
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID: z.string().min(1),
|
||||
});
|
||||
|
||||
export const env = envSchema.parse({
|
||||
NEXT_PUBLIC_DJANGO_API_URL: process.env.NEXT_PUBLIC_DJANGO_API_URL,
|
||||
NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID: process.env.NEXT_PUBLIC_CLOUDFLARE_ACCOUNT_ID,
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 13.8: Error Boundaries & Monitoring (2 hours)
|
||||
|
||||
### Global Error Handling
|
||||
|
||||
```typescript
|
||||
// app/error.tsx
|
||||
'use client';
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button onClick={reset}>Try again</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// app/global-error.tsx (catches errors in root layout)
|
||||
'use client';
|
||||
|
||||
export default function GlobalError({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button onClick={reset}>Try again</button>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Not Found Pages
|
||||
|
||||
```typescript
|
||||
// app/not-found.tsx
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div>
|
||||
<h2>404 - Page Not Found</h2>
|
||||
<p>The page you're looking for doesn't exist.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Checklist
|
||||
- [ ] Error boundaries for each route segment
|
||||
- [ ] Global error handler
|
||||
- [ ] 404 page styled
|
||||
- [ ] Error logging implemented
|
||||
- [ ] User-friendly error messages
|
||||
- [ ] Errors don't expose sensitive data
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Phase Completion Criteria
|
||||
|
||||
### Configuration
|
||||
- [ ] Turbopack fully configured
|
||||
- [ ] Image optimization working
|
||||
- [ ] Environment variables validated
|
||||
- [ ] next.config.js optimized
|
||||
|
||||
### Performance
|
||||
- [ ] Bundle size optimized
|
||||
- [ ] Code splitting implemented
|
||||
- [ ] Images optimized
|
||||
- [ ] Fonts optimized
|
||||
- [ ] Core Web Vitals meet targets
|
||||
|
||||
### SEO
|
||||
- [ ] All pages have metadata
|
||||
- [ ] OpenGraph tags present
|
||||
- [ ] Sitemap generated
|
||||
- [ ] Robots.txt configured
|
||||
|
||||
### User Experience
|
||||
- [ ] Loading states implemented
|
||||
- [ ] Error boundaries working
|
||||
- [ ] 404 pages styled
|
||||
- [ ] No jarring layout shifts
|
||||
|
||||
### Code Quality
|
||||
- [ ] Server/Client components optimized
|
||||
- [ ] No unnecessary 'use client' directives
|
||||
- [ ] Parallel data fetching where possible
|
||||
- [ ] Proper caching strategies
|
||||
|
||||
---
|
||||
|
||||
## 📊 Progress Tracking
|
||||
|
||||
**Started:** [Date]
|
||||
**Completed:** [Date]
|
||||
**Time Spent:** [Hours]
|
||||
|
||||
### Tasks Completed
|
||||
- [ ] Task 13.1: Turbopack Configuration
|
||||
- [ ] Task 13.2: Server vs Client Components
|
||||
- [ ] Task 13.3: Data Fetching Optimization
|
||||
- [ ] Task 13.4: Loading States & Suspense
|
||||
- [ ] Task 13.5: Metadata API for SEO
|
||||
- [ ] Task 13.6: Performance Optimization
|
||||
- [ ] Task 13.7: Environment Variables
|
||||
- [ ] Task 13.8: Error Boundaries
|
||||
|
||||
---
|
||||
|
||||
## ⏭️ Next Phase
|
||||
|
||||
Once this phase is complete, proceed to [Phase 14: Cleanup & Testing](./PHASE_14_CLEANUP_TESTING.md)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [Next.js 15 Migration Guide](./NEXTJS_15_MIGRATION_GUIDE.md)
|
||||
- [Environment Variables Guide](./ENVIRONMENT_VARIABLES.md)
|
||||
- [Bun Setup Guide](./BUN_GUIDE.md)
|
||||
- Next.js Documentation: https://nextjs.org/docs
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** November 9, 2025
|
||||
Reference in New Issue
Block a user