united-tattoo/OPTIMIZATION_GUIDE.md
2025-11-27 04:28:40 -07:00

1429 lines
32 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Next.js Optimization Guide for United Tattoo
## Improving Developer Experience & Site Performance
**Last Updated:** 2025-11-27
**Target Framework:** Next.js 14 (App Router)
**Deployment:** Cloudflare Workers via OpenNext
---
## Table of Contents
1. [Quick Wins (This Week)](#quick-wins-this-week)
2. [Developer Experience Improvements](#developer-experience-improvements)
3. [Performance Optimizations](#performance-optimizations)
4. [Code Quality & Maintainability](#code-quality--maintainability)
5. [Implementation Priority Matrix](#implementation-priority-matrix)
6. [Measuring Success](#measuring-success)
---
## Quick Wins (This Week)
### 1.1 Add MDX Support for Content Pages
**Problem:** Editing content pages requires React boilerplate and components.
**Solution:** Add MDX to write pages in markdown with optional React components.
**Implementation:**
```bash
npm install @next/mdx @mdx-js/loader @mdx-js/react
```
**Create `mdx-components.tsx` in root:**
```typescript
import type { MDXComponents } from 'mdx/types'
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
h1: ({ children }) => <h1 className="text-4xl font-bold mb-4">{children}</h1>,
h2: ({ children }) => <h2 className="text-3xl font-semibold mb-3">{children}</h2>,
p: ({ children }) => <p className="mb-4 leading-relaxed">{children}</p>,
a: ({ href, children }) => <a href={href} className="text-blue-600 hover:underline">{children}</a>,
...components,
}
}
```
**Update `next.config.mjs`:**
```javascript
import createMDX from '@next/mdx'
const withMDX = createMDX({
extension: /\.mdx?$/,
options: {
remarkPlugins: [],
rehypePlugins: [],
},
})
export default withMDX({
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
// ... rest of config
})
```
**Convert pages to MDX:**
```bash
# Example: app/aftercare/page.tsx → app/aftercare/page.mdx
```
**Benefits:**
- ✅ Write content in markdown (much faster)
- ✅ Drop in React components when needed
- ✅ No boilerplate for simple pages
- ⏱️ **Time saved:** 5-10 minutes per content edit
---
### 1.2 Create Content Component Library
**Problem:** Repeating the same UI patterns across pages.
**Solution:** Pre-built, reusable content components.
**Create `components/content/` directory:**
```typescript
// components/content/Section.tsx
export function Section({
title,
children,
className = ""
}: {
title?: string
children: React.ReactNode
className?: string
}) {
return (
<section className={`py-12 ${className}`}>
{title && <h2 className="text-3xl font-bold mb-6">{title}</h2>}
<div className="prose prose-lg max-w-none">
{children}
</div>
</section>
)
}
// components/content/Hero.tsx
export function Hero({
title,
subtitle,
backgroundImage,
cta
}: HeroProps) {
return (
<div
className="relative h-[600px] flex items-center justify-center"
style={{ backgroundImage: `url(${backgroundImage})` }}
>
<div className="text-center text-white z-10">
<h1 className="text-6xl font-bold mb-4">{title}</h1>
{subtitle && <p className="text-xl mb-8">{subtitle}</p>}
{cta && (
<Button href={cta.href} size="lg">
{cta.text}
</Button>
)}
</div>
<div className="absolute inset-0 bg-black/40" />
</div>
)
}
// components/content/Card.tsx
export function Card({
title,
description,
image,
href
}: CardProps) {
return (
<Link href={href} className="block group">
<div className="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-xl transition-shadow">
{image && (
<img
src={image}
alt={title}
className="w-full h-48 object-cover"
/>
)}
<div className="p-6">
<h3 className="text-xl font-semibold mb-2 group-hover:text-blue-600">
{title}
</h3>
{description && (
<p className="text-gray-600">{description}</p>
)}
</div>
</div>
</Link>
)
}
// Export barrel
// components/content/index.ts
export { Section } from './Section'
export { Hero } from './Hero'
export { Card } from './Card'
export { Grid } from './Grid'
export { CallToAction } from './CallToAction'
```
**Usage in pages:**
```tsx
import { Hero, Section, Grid, Card } from '@/components/content'
export default function ServicesPage() {
return (
<>
<Hero
title="Our Services"
subtitle="Professional tattoo artistry"
backgroundImage="/images/hero-services.jpg"
/>
<Section title="What We Offer">
<Grid columns={3}>
<Card
title="Custom Tattoos"
description="Work with our artists to create your dream piece"
image="/images/custom.jpg"
href="/book"
/>
{/* More cards... */}
</Grid>
</Section>
</>
)
}
```
**Benefits:**
- ✅ Consistent UI across site
- ✅ Less code duplication
- ✅ Easier to maintain styles
- ⏱️ **Time saved:** 15-20 minutes per page creation
---
### 1.3 Convert Pages to Server Components
**Problem:** Unnecessary client-side JavaScript on static pages.
**Solution:** Remove `"use client"` from pages that don't need interactivity.
**Audit candidates:**
```bash
# Find all pages with "use client"
grep -r "use client" app/ --include="page.tsx"
```
**Likely candidates for conversion:**
- `app/page.tsx` (homepage) - Most sections can be server-rendered
- `app/artists/page.tsx` (artist listing) - Just displays data
- `app/aftercare/page.tsx` (static content)
- `app/privacy/page.tsx` (static content)
- `app/terms/page.tsx` (static content)
**Before:**
```typescript
"use client"
export default function ArtistsPage() {
const [artists, setArtists] = useState([])
useEffect(() => {
fetch('/api/artists')
.then(r => r.json())
.then(setArtists)
}, [])
return <ArtistGrid artists={artists} />
}
```
**After:**
```typescript
// No "use client" directive - this is a server component
async function getArtists() {
const db = getDB()
return await db.artists.findMany({
where: { isActive: true },
include: { portfolioImages: { take: 6 } }
})
}
export default async function ArtistsPage() {
const artists = await getArtists()
return <ArtistGrid artists={artists} />
}
```
**For interactive parts, create client islands:**
```typescript
// app/artists/page.tsx (server component)
export default async function ArtistsPage() {
const artists = await getArtists()
return (
<div>
<h1>Our Artists</h1>
{/* Client component for filtering only */}
<ArtistFilter />
<ArtistGrid artists={artists} />
</div>
)
}
// components/ArtistFilter.tsx (client component)
"use client"
export function ArtistFilter() {
const [filter, setFilter] = useState('')
// Only the filter is interactive, rest is server-rendered
}
```
**Benefits:**
- ✅ Faster initial page load (less JS to download)
- ✅ Better SEO (fully rendered HTML)
- ✅ Reduced hydration time
- 📉 **Performance:** 30-50% less JavaScript per page
---
### 1.4 Add Loading & Error States
**Problem:** No feedback during data fetching, poor error UX.
**Solution:** Use Next.js 14's `loading.tsx` and `error.tsx` conventions.
**Create loading states:**
```typescript
// app/artists/loading.tsx
export default function Loading() {
return (
<div className="container py-12">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{[1, 2, 3, 4, 5, 6].map((i) => (
<div key={i} className="animate-pulse">
<div className="bg-gray-200 h-64 rounded-lg mb-4" />
<div className="bg-gray-200 h-6 w-3/4 rounded mb-2" />
<div className="bg-gray-200 h-4 w-1/2 rounded" />
</div>
))}
</div>
</div>
)
}
// app/artists/[id]/loading.tsx
export default function Loading() {
return <PortfolioSkeleton />
}
```
**Create error boundaries:**
```typescript
// app/artists/error.tsx
"use client"
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div className="container py-12 text-center">
<h2 className="text-2xl font-bold mb-4">Something went wrong!</h2>
<p className="text-gray-600 mb-6">{error.message}</p>
<button
onClick={reset}
className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700"
>
Try again
</button>
</div>
)
}
```
**Benefits:**
- ✅ Better user experience during loading
- ✅ Graceful error handling
- ✅ No need to manage loading state manually
- ⏱️ **Time saved:** 5-10 minutes per page (no manual loading state)
---
## Developer Experience Improvements
### 2.1 Improve Type Safety
**Current issue:** Some areas lack proper TypeScript types.
**Add Zod schemas for API responses:**
```typescript
// lib/schemas/artist.ts
import { z } from 'zod'
export const artistSchema = z.object({
id: z.string(),
name: z.string(),
slug: z.string(),
bio: z.string().nullable(),
specialties: z.array(z.string()),
instagramHandle: z.string().nullable(),
portfolioImages: z.array(z.object({
id: z.string(),
url: z.string(),
alt: z.string().nullable(),
tags: z.array(z.string()),
})),
})
export type Artist = z.infer<typeof artistSchema>
// Use in API routes for validation
export async function GET() {
const data = await db.artists.findMany()
const validated = z.array(artistSchema).parse(data) // Runtime validation
return Response.json(validated)
}
```
**Generate types from database schema:**
```bash
npm install drizzle-kit
```
```typescript
// scripts/generate-types.ts
import { generateTypes } from 'drizzle-kit'
generateTypes({
schema: './lib/db.ts',
out: './types/database.d.ts'
})
```
**Benefits:**
- ✅ Catch errors at compile time
- ✅ Better autocomplete in IDE
- ✅ Runtime validation of API data
- 🐛 **Fewer bugs** in production
---
### 2.2 Add Development Scripts
**Create helper scripts for common tasks:**
```json
// package.json
{
"scripts": {
"dev:all": "concurrently \"npm run dev\" \"npm run db:studio:local\"",
"dev:debug": "NODE_OPTIONS='--inspect' next dev",
"db:reset:local": "wrangler d1 execute united-tattoo --local --file=./sql/schema.sql && node scripts/seed-local.js",
"db:seed:local": "node scripts/seed-local.js",
"analyze": "ANALYZE=true npm run build",
"type-check:watch": "tsc --noEmit --watch",
"clean": "rm -rf .next .open-next node_modules/.cache",
"fresh": "npm run clean && npm install && npm run dev"
}
}
```
**Create seed script for local development:**
```typescript
// scripts/seed-local.ts
import { getDB } from '@/lib/db'
async function seed() {
const db = getDB()
// Create test user
await db.users.create({
data: {
email: 'test@example.com',
name: 'Test Artist',
role: 'ARTIST',
}
})
// Create test artist
await db.artists.create({
data: {
name: 'Test Artist',
slug: 'test-artist',
bio: 'Test bio',
specialties: ['Realism', 'Color'],
userId: '...',
}
})
console.log('✅ Database seeded!')
}
seed().catch(console.error)
```
**Benefits:**
- ✅ Faster development setup
- ✅ Easy database reset/seeding
- ✅ Better debugging capabilities
- ⏱️ **Time saved:** 10-15 minutes daily on setup tasks
---
### 2.3 Improve Error Messages
**Add better error handling in database layer:**
```typescript
// lib/db.ts
export const db = {
artists: {
async findById(id: string) {
try {
const db = getDB()
const artist = await db.prepare(
'SELECT * FROM artists WHERE id = ?'
).bind(id).first()
if (!artist) {
throw new Error(`Artist not found: ${id}`)
}
return artist
} catch (error) {
// Add context to errors
throw new Error(
`Failed to fetch artist ${id}: ${error.message}`,
{ cause: error }
)
}
}
}
}
```
**Add request logging:**
```typescript
// middleware.ts
export function middleware(request: NextRequest) {
const start = Date.now()
console.log(`→ ${request.method} ${request.nextUrl.pathname}`)
const response = NextResponse.next()
response.headers.set('X-Response-Time', `${Date.now() - start}ms`)
console.log(
`← ${request.method} ${request.nextUrl.pathname} ` +
`(${Date.now() - start}ms)`
)
return response
}
```
**Benefits:**
- ✅ Easier debugging
- ✅ Faster error resolution
- ✅ Better production monitoring
- 🐛 **Faster bug fixes**
---
### 2.4 Create Component Templates
**Add templates for common patterns:**
```bash
# scripts/create-page.sh
#!/bin/bash
PAGE_NAME=$1
ROUTE_PATH=$2
mkdir -p "app/$ROUTE_PATH"
cat > "app/$ROUTE_PATH/page.tsx" <<EOF
import { Metadata } from 'next'
export const metadata: Metadata = {
title: '$PAGE_NAME | United Tattoo',
description: 'Description here',
}
export default async function ${PAGE_NAME}Page() {
return (
<div className="container py-12">
<h1 className="text-4xl font-bold mb-6">$PAGE_NAME</h1>
{/* Content here */}
</div>
)
}
EOF
echo "✅ Created page: app/$ROUTE_PATH/page.tsx"
```
**Usage:**
```bash
npm run create:page "Services" "services"
```
**VSCode snippets:**
```json
// .vscode/snippets.code-snippets
{
"Next.js Page": {
"prefix": "npage",
"body": [
"import { Metadata } from 'next'",
"",
"export const metadata: Metadata = {",
" title: '${1:Page Title} | United Tattoo',",
" description: '${2:Description}',",
"}",
"",
"export default async function ${1}Page() {",
" return (",
" <div className=\"container py-12\">",
" <h1 className=\"text-4xl font-bold mb-6\">${1}</h1>",
" $0",
" </div>",
" )",
"}"
]
},
"API Route": {
"prefix": "napi",
"body": [
"import { NextRequest } from 'next/server'",
"import { getServerSession } from 'next-auth'",
"import { authOptions } from '@/lib/auth'",
"",
"export async function GET(request: NextRequest) {",
" const session = await getServerSession(authOptions)",
" ",
" if (!session) {",
" return Response.json({ error: 'Unauthorized' }, { status: 401 })",
" }",
" ",
" // Logic here",
" $0",
" ",
" return Response.json({ data: null })",
"}"
]
}
}
```
**Benefits:**
- ✅ Consistent code structure
- ✅ Faster file creation
- ✅ Less boilerplate typing
- ⏱️ **Time saved:** 2-5 minutes per file
---
## Performance Optimizations
### 3.1 Optimize Images
**Current issue:** Some images not using Next.js Image component.
**Replace `<img>` with `<Image>`:**
```typescript
// Before
<img src="/images/hero.jpg" alt="Hero" />
// After
import Image from 'next/image'
<Image
src="/images/hero.jpg"
alt="Hero"
width={1920}
height={1080}
priority // For above-the-fold images
placeholder="blur"
blurDataURL="data:image/..." // Optional blur-up effect
/>
```
**For portfolio images from R2:**
```typescript
// components/PortfolioImage.tsx
import Image from 'next/image'
export function PortfolioImage({ image }: { image: PortfolioImage }) {
return (
<Image
src={image.url}
alt={image.alt || image.title}
width={800}
height={600}
className="rounded-lg shadow-md"
loading="lazy" // Lazy load below fold
quality={85} // Reduce quality slightly for smaller files
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
)
}
```
**Generate blur placeholders:**
```bash
npm install plaiceholder
```
```typescript
// lib/blur-image.ts
import { getPlaiceholder } from 'plaiceholder'
export async function getBlurDataURL(src: string) {
try {
const buffer = await fetch(src).then(r => r.arrayBuffer())
const { base64 } = await getPlaiceholder(Buffer.from(buffer))
return base64
} catch {
return undefined
}
}
```
**Benefits:**
- 📉 **30-50% smaller image sizes**
- ✅ Automatic WebP/AVIF conversion
- ✅ Responsive images
- ✅ Better CLS (Cumulative Layout Shift)
---
### 3.2 Add Route-Level Caching
**Add caching headers to static content:**
```typescript
// app/artists/page.tsx
export const revalidate = 3600 // Revalidate every hour
export default async function ArtistsPage() {
const artists = await getArtists()
return <ArtistGrid artists={artists} />
}
// app/artists/[id]/page.tsx
export const revalidate = 1800 // 30 minutes
export async function generateStaticParams() {
const artists = await getArtists()
return artists.map(a => ({ id: a.slug }))
}
```
**Add API route caching:**
```typescript
// app/api/artists/route.ts
export async function GET() {
const artists = await db.artists.findMany()
return Response.json(artists, {
headers: {
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
}
})
}
```
**Add Cloudflare KV caching for expensive operations:**
```typescript
// lib/cache.ts
export async function cached<T>(
key: string,
fn: () => Promise<T>,
ttl: number = 3600
): Promise<T> {
const kv = getKVNamespace()
// Try cache first
const cached = await kv.get(key, 'json')
if (cached) return cached as T
// Execute and cache
const result = await fn()
await kv.put(key, JSON.stringify(result), { expirationTtl: ttl })
return result
}
// Usage
const artists = await cached('artists:all', () => db.artists.findMany(), 3600)
```
**Benefits:**
- 📉 **50-90% faster repeat visits**
- ✅ Reduced database load
- ✅ Better scalability
- 💰 **Lower hosting costs**
---
### 3.3 Optimize Fonts
**Use next/font with local fonts:**
```typescript
// app/layout.tsx
import { Inter, Playfair_Display } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
display: 'swap', // Prevent FOIT (Flash of Invisible Text)
})
const playfair = Playfair_Display({
subsets: ['latin'],
variable: '--font-playfair',
display: 'swap',
})
export default function RootLayout({ children }) {
return (
<html className={`${inter.variable} ${playfair.variable}`}>
<body className="font-sans">{children}</body>
</html>
)
}
```
**Update Tailwind config:**
```javascript
// tailwind.config.ts
export default {
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)', 'sans-serif'],
serif: ['var(--font-playfair)', 'serif'],
},
},
},
}
```
**Benefits:**
- 📉 **No layout shift from font loading**
- ✅ Automatic font subsetting
- ✅ Preloading and optimization
-**Faster perceived load time**
---
### 3.4 Code Splitting & Lazy Loading
**Lazy load heavy components:**
```typescript
// Before - BookingForm loaded immediately
import { BookingForm } from '@/components/BookingForm'
export default function BookPage() {
return <BookingForm />
}
// After - BookingForm loaded on demand
import dynamic from 'next/dynamic'
const BookingForm = dynamic(
() => import('@/components/BookingForm'),
{
loading: () => <LoadingSpinner />,
ssr: false // Client-only if needed
}
)
export default function BookPage() {
return <BookingForm />
}
```
**Lazy load admin dashboard components:**
```typescript
// app/admin/page.tsx
import dynamic from 'next/dynamic'
const AnalyticsDashboard = dynamic(() => import('@/components/admin/AnalyticsDashboard'))
const PortfolioManager = dynamic(() => import('@/components/admin/PortfolioManager'))
const CalendarManager = dynamic(() => import('@/components/admin/CalendarManager'))
export default function AdminPage() {
return (
<div>
<AnalyticsDashboard />
<PortfolioManager />
<CalendarManager />
</div>
)
}
```
**Benefits:**
- 📉 **40-60% smaller initial bundle**
- ✅ Faster time to interactive
- ✅ Better mobile performance
-**Lighthouse score: +10-20 points**
---
### 3.5 Optimize Third-Party Scripts
**Use next/script for external scripts:**
```typescript
// app/layout.tsx
import Script from 'next/script'
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
{/* Analytics - load after page interactive */}
<Script
src="https://analytics.example.com/script.js"
strategy="afterInteractive"
/>
{/* Non-critical - load lazily */}
<Script
src="https://widget.example.com/chat.js"
strategy="lazyOnload"
/>
</body>
</html>
)
}
```
**Defer non-essential features:**
```typescript
// components/ChatWidget.tsx
"use client"
import { useEffect, useState } from 'react'
export function ChatWidget() {
const [loaded, setLoaded] = useState(false)
useEffect(() => {
// Load chat widget after 5 seconds or user interaction
const timer = setTimeout(() => setLoaded(true), 5000)
const loadOnInteraction = () => {
setLoaded(true)
clearTimeout(timer)
}
window.addEventListener('scroll', loadOnInteraction, { once: true })
window.addEventListener('click', loadOnInteraction, { once: true })
return () => clearTimeout(timer)
}, [])
if (!loaded) return null
return <div id="chat-widget">{/* Widget here */}</div>
}
```
**Benefits:**
- 📉 **Faster initial page load**
- ✅ Better Core Web Vitals
- ✅ Non-blocking script loading
-**Improved FCP and TTI**
---
### 3.6 Database Query Optimization
**Add indexes to frequently queried columns:**
```sql
-- sql/migrations/YYYYMMDD_0001_add_indexes.sql
-- Speed up artist lookups by slug
CREATE INDEX IF NOT EXISTS idx_artists_slug ON artists(slug);
-- Speed up portfolio queries
CREATE INDEX IF NOT EXISTS idx_portfolio_artist_id ON portfolio_images(artist_id);
CREATE INDEX IF NOT EXISTS idx_portfolio_sort ON portfolio_images(artist_id, sort_order);
-- Speed up appointment queries
CREATE INDEX IF NOT EXISTS idx_appointments_artist_date ON appointments(artist_id, appointment_date);
CREATE INDEX IF NOT EXISTS idx_appointments_status ON appointments(status);
-- Speed up user lookups
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
```
**Optimize queries with JOINs:**
```typescript
// Before - Multiple queries (N+1 problem)
const artists = await db.artists.findMany()
for (const artist of artists) {
artist.images = await db.portfolioImages.findMany({
where: { artistId: artist.id }
})
}
// After - Single query with JOIN
const artists = await db.prepare(`
SELECT
a.*,
json_group_array(
json_object(
'id', pi.id,
'url', pi.url,
'alt', pi.alt
)
) as images
FROM artists a
LEFT JOIN portfolio_images pi ON pi.artist_id = a.id
GROUP BY a.id
`).all()
```
**Add prepared statement caching:**
```typescript
// lib/db.ts
const statementCache = new Map()
export function getCachedStatement(sql: string) {
if (!statementCache.has(sql)) {
const db = getDB()
statementCache.set(sql, db.prepare(sql))
}
return statementCache.get(sql)
}
// Usage
const stmt = getCachedStatement('SELECT * FROM artists WHERE slug = ?')
const artist = await stmt.bind(slug).first()
```
**Benefits:**
- 📉 **50-80% faster queries**
- ✅ Reduced database load
- ✅ Better scalability
-**Faster API responses**
---
### 3.7 Optimize CSS
**Use Tailwind's JIT mode (already enabled but optimize):**
```javascript
// tailwind.config.ts
export default {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
// Don't include node_modules unless needed
],
theme: {
extend: {
// Only include custom values you actually use
},
},
plugins: [],
}
```
**Extract critical CSS for above-the-fold:**
```bash
npm install critical
```
```typescript
// next.config.mjs
export default {
experimental: {
optimizeCss: true, // Enable CSS optimization
},
}
```
**Remove unused ShadCN components:**
```bash
# Audit which components are actually used
grep -r "from '@/components/ui'" app/ components/ --no-filename | \
sed 's/.*from//' | sort | uniq
```
**Benefits:**
- 📉 **20-40% smaller CSS bundle**
- ✅ Faster first paint
- ✅ Better mobile performance
-**Improved FCP**
---
## Code Quality & Maintainability
### 4.1 Consistent File Structure
**Establish clear conventions:**
```
app/
├── (marketing)/ # Public pages
│ ├── page.tsx # Homepage
│ ├── artists/ # Artist pages
│ ├── book/ # Booking
│ └── [slug]/ # Dynamic content pages
├── (dashboard)/ # Protected pages
│ ├── admin/ # Admin dashboard
│ └── artist-dashboard/ # Artist dashboard
└── api/ # API routes
├── artists/
├── appointments/
└── admin/
components/
├── ui/ # ShadCN components
├── content/ # Content components (NEW)
├── admin/ # Admin-specific
└── shared/ # Reusable components
lib/
├── db.ts # Database layer
├── auth.ts # Authentication
├── utils/ # Utilities
│ ├── date.ts
│ ├── format.ts
│ └── validation.ts
└── hooks/ # Custom React hooks
```
**Benefits:**
- ✅ Easier to find files
- ✅ Clear separation of concerns
- ✅ Onboarding new developers faster
---
### 4.2 Add Documentation
**Create component documentation:**
```typescript
// components/content/Hero.tsx
/**
* Hero section component for top of pages
*
* @example
* ```tsx
* <Hero
* title="Welcome"
* subtitle="Your journey starts here"
* backgroundImage="/images/hero.jpg"
* cta={{ text: "Get Started", href: "/book" }}
* />
* ```
*/
export function Hero({
title,
subtitle,
backgroundImage,
cta
}: HeroProps) {
// Implementation
}
```
**Add README files:**
```markdown
<!-- components/content/README.md -->
# Content Components
Pre-built components for quickly creating marketing pages.
## Available Components
### Hero
Full-width hero section with background image and CTA.
**Props:**
- `title` (string) - Main heading
- `subtitle` (string, optional) - Subheading
- `backgroundImage` (string) - Image URL
- `cta` (object, optional) - Call to action button
**Example:**
...
```
**Benefits:**
- ✅ Faster onboarding
- ✅ Less time answering questions
- ✅ Consistent component usage
---
### 4.3 Add Storybook (Optional)
**For visual component development:**
```bash
npx storybook@latest init
```
```typescript
// components/content/Hero.stories.tsx
import type { Meta, StoryObj } from '@storybook/react'
import { Hero } from './Hero'
const meta: Meta<typeof Hero> = {
title: 'Content/Hero',
component: Hero,
}
export default meta
type Story = StoryObj<typeof Hero>
export const Default: Story = {
args: {
title: 'Welcome to United Tattoo',
subtitle: 'Professional artistry in Fountain, CO',
backgroundImage: '/images/hero.jpg',
cta: {
text: 'Book Now',
href: '/book',
},
},
}
export const WithoutSubtitle: Story = {
args: {
title: 'Our Services',
backgroundImage: '/images/services.jpg',
},
}
```
**Benefits:**
- ✅ Visual component library
- ✅ Test components in isolation
- ✅ Faster iteration on UI
- 🎨 **Better design system**
---
## Implementation Priority Matrix
### Priority 1: Quick Wins (Week 1)
**High Impact, Low Effort**
- ✅ Add MDX support (1-2 hours)
- ✅ Create content component library (3-4 hours)
- ✅ Convert pages to server components (2-3 hours)
- ✅ Add loading/error states (1-2 hours)
- ✅ Optimize images (2-3 hours)
**Total: ~12-16 hours**
**Impact: Major DX improvement + 20-30% performance boost**
---
### Priority 2: Performance (Week 2-3)
**High Impact, Medium Effort**
- ✅ Add route-level caching (3-4 hours)
- ✅ Database indexes (1-2 hours)
- ✅ Code splitting & lazy loading (4-5 hours)
- ✅ Optimize fonts (1-2 hours)
- ✅ Optimize third-party scripts (2-3 hours)
**Total: ~12-16 hours**
**Impact: 40-60% performance improvement**
---
### Priority 3: Code Quality (Week 3-4)
**Medium Impact, Low-Medium Effort**
- ✅ Improve type safety (4-5 hours)
- ✅ Add development scripts (2-3 hours)
- ✅ Better error messages (3-4 hours)
- ✅ Component templates (2-3 hours)
- ✅ File structure cleanup (3-4 hours)
**Total: ~14-19 hours**
**Impact: Better maintainability, fewer bugs**
---
### Priority 4: Documentation (Ongoing)
**Lower Impact, Low Effort**
- ✅ Component documentation (1-2 hours/week)
- ✅ Add README files (1-2 hours)
- ✅ Storybook setup (4-6 hours, optional)
**Total: ~6-10 hours**
**Impact: Easier onboarding, consistent usage**
---
## Measuring Success
### Developer Experience Metrics
**Before:**
- ⏱️ Time to create new page: 30-45 minutes
- ⏱️ Time to add content section: 15-20 minutes
- 🐛 Time to debug error: 20-30 minutes
- 📚 Documentation: Minimal
**After (Target):**
- ⏱️ Time to create new page: **10-15 minutes** (50-60% faster)
- ⏱️ Time to add content section: **5-10 minutes** (50% faster)
- 🐛 Time to debug error: **10-15 minutes** (40% faster)
- 📚 Documentation: Comprehensive
---
### Performance Metrics
**Measure with Lighthouse:**
```bash
# Install Lighthouse CLI
npm install -g lighthouse
# Run audit
lighthouse https://united-tattoos.com --view
```
**Target improvements:**
| Metric | Current | Target | Improvement |
|--------|---------|--------|-------------|
| Performance Score | ~75 | 90+ | +15-20 |
| FCP (First Contentful Paint) | ~2.5s | <1.5s | 40% faster |
| LCP (Largest Contentful Paint) | ~4.0s | <2.5s | 38% faster |
| TTI (Time to Interactive) | ~5.0s | <3.0s | 40% faster |
| CLS (Cumulative Layout Shift) | ~0.15 | <0.1 | 33% better |
| Bundle Size (JS) | ~800KB | <500KB | 38% smaller |
---
### User Experience Metrics
**Track with analytics:**
- Bounce rate: Target <40% (currently ~50%)
- 📱 Mobile conversion rate: Target +20%
- Average session duration: Target +30%
- 🔄 Repeat visitor rate: Target +25%
---
## Next Steps
### Week 1: Quick Wins
1. Monday: Add MDX support + convert 3 static pages
2. Tuesday: Create content component library
3. Wednesday: Convert 5 pages to server components
4. Thursday: Add loading/error states across app
5. Friday: Optimize all images
### Week 2: Performance
1. Monday: Add route-level caching
2. Tuesday: Database indexes + query optimization
3. Wednesday: Code splitting for admin dashboard
4. Thursday: Lazy load heavy components
5. Friday: Optimize fonts + third-party scripts
### Week 3: Code Quality
1. Monday: Improve type safety with Zod
2. Tuesday: Add development scripts
3. Wednesday: Better error messages + logging
4. Thursday: Create component templates
5. Friday: File structure cleanup
### Week 4: Documentation & Polish
1. Monday-Wednesday: Add documentation
2. Thursday: Storybook setup (optional)
3. Friday: Measure results, create performance report
---
## Conclusion
By implementing these optimizations, you'll achieve:
**Developer Experience:**
- 50-60% faster page creation
- Much easier content editing with MDX
- Less boilerplate and complexity
- Better debugging and error messages
**Performance:**
- 📉 40-60% smaller JavaScript bundles
- 30-40% faster page loads
- Better Core Web Vitals
- 💰 Lower hosting costs
**Maintainability:**
- Better code organization
- Comprehensive documentation
- Fewer bugs in production
- Easier onboarding
**Total Investment:** ~40-60 hours over 4 weeks
**Long-term Savings:** ~5-10 hours/week in development time
This positions you well for the future - whether you eventually migrate to Astro or stay with Next.js, you'll have a cleaner, faster, more maintainable codebase.