Developer Guide

Overview

Next.js 15 App Router structure

Frontend Overview

The Prompt Stack frontend is built with Next.js 15, React 19, and TypeScript. It's designed for rapid development with pre-built components and patterns.

Technology Stack

Core Technologies

  • ⚔ Next.js 15 (App Router)
  • āš›ļø React 19 RC
  • šŸ“˜ TypeScript 5.3+
  • šŸŽØ Tailwind CSS 3.4
  • šŸ” Supabase Auth

Key Libraries

  • šŸ“Š SWR (data fetching)
  • šŸŽ­ Framer Motion
  • šŸŽÆ Lucide Icons
  • šŸŒ™ next-themes
  • 🧩 Radix UI primitives

App Router Structure

app/
ā”œā”€ā”€ (authenticated)/     # Protected routes group
│   ā”œā”€ā”€ layout.tsx      # Auth wrapper
│   ā”œā”€ā”€ dashboard/      # User dashboard
│   ā”œā”€ā”€ profile/        # User profile
│   └── settings/       # App settings
ā”œā”€ā”€ auth/               # Public auth pages
│   ā”œā”€ā”€ login/
│   ā”œā”€ā”€ register/
│   └── callback/
ā”œā”€ā”€ api/                # API route handlers
ā”œā”€ā”€ layout.tsx          # Root layout
ā”œā”€ā”€ page.tsx            # Homepage
└── globals.css         # Global styles

Creating Pages

Basic Page

Create a new file at app/my-page/page.tsx:

export default function MyPage() {
  return (
    <div className="container mx-auto py-8">
      <h1 className="text-3xl font-bold mb-4">My Page</h1>
      <p>This page is automatically available at /my-page</p>
    </div>
  )
}

Protected Page

Create a file at app/(authenticated)/my-feature/page.tsx:

'use client'

import { useAuth } from '@/components/providers/auth-provider'

export default function MyFeaturePage() {
  const { user, loading } = useAuth()
  
  if (loading) {
    return <div>Loading...</div>
  }
  
  return (
    <div className="container mx-auto py-8">
      <h1 className="text-3xl font-bold mb-4">
        Welcome, {user?.email}
      </h1>
      <p>This page requires authentication</p>
    </div>
  )
}

šŸ”’ Security Note: Pages in the (authenticated) folder are automatically protected. The layout handles redirects for unauthenticated users.

Component Architecture

Component Organization

components/
ā”œā”€ā”€ ui/                 # Basic UI components
│   ā”œā”€ā”€ button.tsx
│   ā”œā”€ā”€ card.tsx
│   ā”œā”€ā”€ input.tsx
│   └── alert.tsx
ā”œā”€ā”€ forms/              # Form components
│   ā”œā”€ā”€ Input.tsx
│   ā”œā”€ā”€ Checkbox.tsx
│   └── FormGroup.tsx
ā”œā”€ā”€ layout/             # Layout components
│   ā”œā”€ā”€ navigation.tsx
│   └── footer.tsx
└── providers/          # React Context providers
    ā”œā”€ā”€ auth-provider.tsx
    └── theme-provider.tsx

Creating a Component

// components/ui/feature-card.tsx
interface FeatureCardProps {
  title: string
  description: string
  icon?: React.ReactNode
  href?: string
}

export function FeatureCard({ 
  title, 
  description, 
  icon,
  href 
}: FeatureCardProps) {
  const content = (
    <>
      {icon && (
        <div className="mb-4 text-accent">
          {icon}
        </div>
      )}
      <h3 className="text-lg font-semibold mb-2">
        {title}
      </h3>
      <p className="text-sm text-muted-foreground">
        {description}
      </p>
    </>
  )
  
  if (href) {
    return (
      <Link 
        href={href}
        className="block p-6 border rounded-lg hover:shadow-lg transition-shadow"
      >
        {content}
      </Link>
    )
  }
  
  return (
    <div className="p-6 border rounded-lg">
      {content}
    </div>
  )
}

State Management

Local State

const [count, setCount] = useState(0)
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(false)

Global State (Context)

// Using the auth context
const { user, signIn, signOut } = useAuth()

// Using the theme context  
const { theme, setTheme } = useTheme()

Server State (SWR)

import useSWR from 'swr'

function Profile() {
  const { data, error, isLoading } = useSWR(
    '/api/user/profile',
    fetcher
  )
  
  if (error) return <div>Failed to load</div>
  if (isLoading) return <div>Loading...</div>
  
  return <div>Hello {data.name}!</div>
}

Styling

Tailwind CSS

Use utility classes for styling:

<div className="max-w-4xl mx-auto px-4 py-8">
  <h1 className="text-3xl font-bold text-foreground mb-4">
    Title
  </h1>
  <p className="text-muted-foreground">
    Description text
  </p>
  <button className="mt-4 px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90">
    Click me
  </button>
</div>

Dark Mode

All components support dark mode automatically:

// Colors adapt to theme
<div className="bg-background text-foreground">
  <div className="bg-muted text-muted-foreground">
    Subtle background
  </div>
  <div className="border border-border">
    With border
  </div>
</div>

Data Fetching

Client-Side Fetching

'use client'

export default function ClientPage() {
  const [data, setData] = useState(null)
  
  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(setData)
  }, [])
  
  return <div>{data}</div>
}

Server-Side Fetching

// Server component (default)
async function getData() {
  const res = await fetch('http://localhost:8000/api/data')
  return res.json()
}

export default async function ServerPage() {
  const data = await getData()
  
  return <div>{data}</div>
}

Forms

Basic Form Example

'use client'

import { useState } from 'react'
import { Input } from '@/components/forms'

export default function ContactForm() {
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState('')
  
  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    setLoading(true)
    setError('')
    
    const formData = new FormData(e.currentTarget)
    
    try {
      const res = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name: formData.get('name'),
          email: formData.get('email'),
          message: formData.get('message')
        })
      })
      
      const data = await res.json()
      
      if (!data.success) {
        throw new Error(data.message)
      }
      
      // Success!
      alert('Message sent!')
    } catch (err) {
      setError(err.message)
    } finally {
      setLoading(false)
    }
  }
  
  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <Input
        name="name"
        label="Name"
        required
      />
      
      <Input
        name="email"
        type="email"
        label="Email"
        required
      />
      
      <textarea
        name="message"
        className="w-full px-3 py-2 border rounded-md"
        rows={4}
        required
      />
      
      {error && (
        <div className="text-red-500 text-sm">{error}</div>
      )}
      
      <button
        type="submit"
        disabled={loading}
        className="px-4 py-2 bg-primary text-white rounded-md disabled:opacity-50"
      >
        {loading ? 'Sending...' : 'Send Message'}
      </button>
    </form>
  )
}

Error Handling

Error Boundaries

Create an error.tsx file in any route folder:

'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div className="flex flex-col items-center justify-center min-h-[400px]">
      <h2 className="text-2xl font-bold mb-4">Something went wrong!</h2>
      <p className="text-muted-foreground mb-4">{error.message}</p>
      <button
        onClick={reset}
        className="px-4 py-2 bg-primary text-white rounded-md"
      >
        Try again
      </button>
    </div>
  )
}

Performance Tips

šŸš€

Use Server Components by default

Only add 'use client' when you need interactivity

šŸ“¦

Lazy load heavy components

Use dynamic imports for large components

šŸ–¼ļø

Optimize images

Use next/image for automatic optimization

⚔

Minimize client-side JavaScript

Server components = faster page loads

šŸŽÆ Frontend Best Practices

  • Always use TypeScript for type safety
  • Follow the component patterns in the codebase
  • Use Tailwind utilities instead of custom CSS
  • Keep components small and focused
  • Handle loading and error states properly