Claude Cursor opencode Skill

web-frameworks

Build modern full-stack web applications with Next.js (App Router, Server Components, RSC, PPR, SSR, SSG, ISR), Turborepo (monorepo management, task pipelines, remote caching, parallel execution), and RemixIcon (3100+ SVG icons in outlined/filled styles). Use when creating React

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download vodailocz-kilo-kit-mcp-skills_engineering_web-frameworks-0448e6c.zip · 46 KB
Part of vodailocz/kilo-kit-mcp — 142 skills

Install

skills CLI npx skills add https://github.com/VoDaiLocz/kilo-kit-mcp/tree/main/skills/engineering/web-frameworks
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vodailocz-kilo-kit-mcp@llmmart
Git git clone https://github.com/VoDaiLocz/kilo-kit-mcp.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vodailocz/kilo-kit-mcp collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Web Frameworks Skill Group

Comprehensive guide for building modern full-stack web applications using Next.js, Turborepo, and RemixIcon.

Overview

This skill group combines three powerful tools for web development:

Next.js - React framework with SSR, SSG, RSC, and optimization features Turborepo - High-performance monorepo build system for JavaScript/TypeScript RemixIcon - Icon library with 3,100+ outlined and filled style icons

When to Use This Skill Group

  • Building new full-stack web applications with modern React
  • Setting up monorepos with multiple apps and shared packages
  • Implementing server-side rendering and static generation
  • Optimizing build performance with intelligent caching
  • Creating consistent UI with professional iconography
  • Managing workspace dependencies across multiple projects
  • Deploying production-ready applications with proper optimization

Stack Selection Guide

Single Application: Next.js + RemixIcon

Use when building a standalone application:

  • E-commerce sites
  • Marketing websites
  • SaaS applications
  • Documentation sites
  • Blogs and content platforms

Setup:

npx create-next-app@latest my-app
cd my-app
npm install remixicon

Monorepo: Next.js + Turborepo + RemixIcon

Use when building multiple applications with shared code:

  • Microfrontends
  • Multi-tenant platforms
  • Internal tools with shared component library
  • Multiple apps (web, admin, mobile-web) sharing logic
  • Design system with documentation site

Setup:

npx create-turbo@latest my-monorepo
# Then configure Next.js apps in apps/ directory
# Install remixicon in shared UI packages

Framework Features Comparison

Feature Next.js Turborepo RemixIcon
Primary Use Web framework Build system UI icons
Best For SSR/SSG apps Monorepos Consistent iconography
Performance Built-in optimization Caching & parallel tasks Lightweight fonts/SVG
TypeScript Full support Full support Type definitions available

Quick Start

Next.js Application

# Create new project
npx create-next-app@latest my-app
cd my-app

# Install RemixIcon
npm install remixicon

# Import in layout
# app/layout.tsx
import 'remixicon/fonts/remixicon.css'

# Start development
npm run dev

Turborepo Monorepo

# Create monorepo
npx create-turbo@latest my-monorepo
cd my-monorepo

# Structure:
# apps/web/          - Next.js application
# apps/docs/         - Documentation site
# packages/ui/       - Shared components with RemixIcon
# packages/config/   - Shared configs
# turbo.json         - Pipeline configuration

# Run all apps
npm run dev

# Build all packages
npm run build

RemixIcon Integration

// Webfont (HTML/CSS)
<i className="ri-home-line"></i>
<i className="ri-search-fill ri-2x"></i>

// React component
import { RiHomeLine, RiSearchFill } from "@remixicon/react"
<RiHomeLine size={24} />
<RiSearchFill size={32} color="blue" />

Reference Navigation

Next.js References:

Turborepo References:

RemixIcon References:

Common Patterns & Workflows

Pattern 1: Full-Stack Monorepo

my-monorepo/
├── apps/
│   ├── web/              # Customer-facing Next.js app
│   ├── admin/            # Admin dashboard Next.js app
│   └── docs/             # Documentation site
├── packages/
│   ├── ui/               # Shared UI with RemixIcon
│   ├── api-client/       # API client library
│   ├── config/           # ESLint, TypeScript configs
│   └── types/            # Shared TypeScript types
└── turbo.json            # Build pipeline

turbo.json:

{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "!.next/cache/**", "dist/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {},
    "test": {
      "dependsOn": ["build"]
    }
  }
}

Pattern 2: Shared Component Library

// packages/ui/src/button.tsx
import { RiLoader4Line } from "@remixicon/react"

export function Button({ children, loading, icon }) {
  return (
    <button>
      {loading ? <RiLoader4Line className="animate-spin" /> : icon}
      {children}
    </button>
  )
}

// apps/web/app/page.tsx
import { Button } from "@repo/ui/button"
import { RiHomeLine } from "@remixicon/react"

export default function Page() {
  return <Button icon={<RiHomeLine />}>Home</Button>
}

Pattern 3: Optimized Data Fetching

// app/posts/[slug]/page.tsx
import { notFound } from 'next/navigation'

// Static generation at build time
export async function generateStaticParams() {
  const posts = await getPosts()
  return posts.map(post => ({ slug: post.slug }))
}

// Revalidate every hour
async function getPost(slug: string) {
  const res = await fetch(`https://api.example.com/posts/${slug}`, {
    next: { revalidate: 3600 }
  })
  if (!res.ok) return null
  return res.json()
}

export default async function Post({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)
  if (!post) notFound()

  return <article>{post.content}</article>
}

Pattern 4: Monorepo CI/CD Pipeline

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 18
      - run: npm install
      - run: npx turbo run build test lint
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}

Utility Scripts

Python utilities in scripts/ directory:

nextjs-init.py - Initialize Next.js project with best practices turborepo-migrate.py - Convert existing monorepo to Turborepo

Usage examples:

# Initialize new Next.js app with TypeScript and recommended setup
python scripts/nextjs-init.py --name my-app --typescript --app-router

# Migrate existing monorepo to Turborepo with dry-run
python scripts/turborepo-migrate.py --path ./my-monorepo --dry-run

# Run tests
cd scripts/tests
pytest

Best Practices

Next.js:

  • Default to Server Components, use Client Components only when needed
  • Implement proper loading and error states
  • Use Image component for automatic optimization
  • Set proper metadata for SEO
  • Leverage caching strategies (force-cache, revalidate, no-store)

Turborepo:

  • Structure monorepo with clear separation (apps/, packages/)
  • Define task dependencies correctly (^build for topological)
  • Configure outputs for proper caching
  • Enable remote caching for team collaboration
  • Use filters to run tasks on changed packages only

RemixIcon:

  • Use line style for minimal interfaces, fill for emphasis
  • Maintain 24x24 grid alignment for crisp rendering
  • Provide aria-labels for accessibility
  • Use currentColor for flexible theming
  • Prefer webfonts for multiple icons, SVG for single icons

Resources

Implementation Checklist

Building with this stack:

  • Create project structure (single app or monorepo)
  • Configure TypeScript and ESLint
  • Set up Next.js with App Router
  • Configure Turborepo pipeline (if monorepo)
  • Install and configure RemixIcon
  • Implement routing and layouts
  • Add loading and error states
  • Configure image and font optimization
  • Set up data fetching patterns
  • Configure caching strategies
  • Add API routes as needed
  • Implement shared component library (if monorepo)
  • Configure remote caching (if monorepo)
  • Set up CI/CD pipeline
  • Configure deployment platform
Files (kilo-kit-mcp)
  • references
    • nextjs-app-router.md 9.1 KB
      # Next.js App Router Architecture
      
      Modern file-system based routing with React Server Components support.
      
      ## File Conventions
      
      Special files define route behavior:
      
      - `page.tsx` - Page UI, makes route publicly accessible
      - `layout.tsx` - Shared UI wrapper for segment and children
      - `loading.tsx` - Loading UI, automatically wraps page in Suspense
      - `error.tsx` - Error UI, wraps page in Error Boundary
      - `not-found.tsx` - 404 UI for route segment
      - `route.ts` - API endpoint (Route Handler)
      - `template.tsx` - Re-rendered layout (doesn't preserve state)
      - `default.tsx` - Fallback for parallel routes
      
      ## Basic Routing
      
      ### Static Routes
      
      ```
      app/
      ├── page.tsx              → /
      ├── about/
      │   └── page.tsx         → /about
      ├── blog/
      │   └── page.tsx         → /blog
      └── contact/
          └── page.tsx         → /contact
      ```
      
      ### Dynamic Routes
      
      Single parameter:
      ```tsx
      // app/blog/[slug]/page.tsx
      export default function BlogPost({ params }: { params: { slug: string } }) {
        return <h1>Post: {params.slug}</h1>
      }
      // Matches: /blog/hello-world, /blog/my-post
      ```
      
      Catch-all segments:
      ```tsx
      // app/shop/[...slug]/page.tsx
      export default function Shop({ params }: { params: { slug: string[] } }) {
        return <h1>Category: {params.slug.join('/')}</h1>
      }
      // Matches: /shop/clothes, /shop/clothes/shirts, /shop/clothes/shirts/red
      ```
      
      Optional catch-all:
      ```tsx
      // app/docs/[[...slug]]/page.tsx
      // Matches: /docs, /docs/getting-started, /docs/api/reference
      ```
      
      ## Layouts
      
      ### Root Layout (Required)
      
      Must include `<html>` and `<body>` tags:
      
      ```tsx
      // app/layout.tsx
      export default function RootLayout({
        children,
      }: {
        children: React.ReactNode
      }) {
        return (
          <html lang="en">
            <body>
              <header>Global Header</header>
              {children}
              <footer>Global Footer</footer>
            </body>
          </html>
        )
      }
      ```
      
      ### Nested Layouts
      
      ```tsx
      // app/dashboard/layout.tsx
      export default function DashboardLayout({
        children,
      }: {
        children: React.ReactNode
      }) {
        return (
          <div>
            <nav>Dashboard Navigation</nav>
            <main>{children}</main>
          </div>
        )
      }
      ```
      
      Layout characteristics:
      - Preserve state during navigation
      - Do not re-render on navigation between child routes
      - Can fetch data
      - Cannot access pathname or searchParams (use Client Component)
      
      ## Route Groups
      
      Organize routes without affecting URL structure:
      
      ```
      app/
      ├── (marketing)/          # Group without URL segment
      │   ├── about/page.tsx   → /about
      │   ├── blog/page.tsx    → /blog
      │   └── layout.tsx       # Marketing layout
      ├── (shop)/
      │   ├── products/page.tsx → /products
      │   ├── cart/page.tsx     → /cart
      │   └── layout.tsx       # Shop layout
      └── layout.tsx           # Root layout
      ```
      
      Use cases:
      - Multiple root layouts
      - Organize code by feature/team
      - Different layouts for different sections
      
      ## Parallel Routes
      
      Render multiple pages simultaneously in same layout:
      
      ```
      app/
      ├── @team/               # Named slot
      │   └── page.tsx
      ├── @analytics/          # Named slot
      │   └── page.tsx
      ├── page.tsx             # Default children
      └── layout.tsx           # Consumes slots
      ```
      
      ```tsx
      // app/layout.tsx
      export default function Layout({
        children,
        team,
        analytics,
      }: {
        children: React.ReactNode
        team: React.ReactNode
        analytics: React.ReactNode
      }) {
        return (
          <>
            {children}
            <div className="grid grid-cols-2">
              {team}
              {analytics}
            </div>
          </>
        )
      }
      ```
      
      Use cases:
      - Split views (dashboards)
      - Modals
      - Conditional rendering based on auth state
      
      ## Intercepting Routes
      
      Intercept navigation to show content in different context:
      
      ```
      app/
      ├── feed/
      │   └── page.tsx
      ├── photo/
      │   └── [id]/
      │       └── page.tsx      # Full photo page
      └── (..)photo/            # Intercepts /photo/[id]
          └── [id]/
              └── page.tsx      # Modal photo view
      ```
      
      Matching conventions:
      - `(.)` - Match same level
      - `(..)` - Match one level above
      - `(..)(..)` - Match two levels above
      - `(...)` - Match from app root
      
      Use case: Display modal when navigating from feed, show full page when URL accessed directly
      
      ## Loading States
      
      ### Loading File
      
      Automatically wraps page in Suspense:
      
      ```tsx
      // app/dashboard/loading.tsx
      export default function Loading() {
        return <div className="spinner">Loading dashboard...</div>
      }
      ```
      
      ### Manual Suspense
      
      Fine-grained control:
      
      ```tsx
      // app/page.tsx
      import { Suspense } from 'react'
      
      async function Posts() {
        const posts = await fetchPosts()
        return <PostsList posts={posts} />
      }
      
      export default function Page() {
        return (
          <div>
            <h1>My Blog</h1>
            <Suspense fallback={<div>Loading posts...</div>}>
              <Posts />
            </Suspense>
          </div>
        )
      }
      ```
      
      ## Error Handling
      
      ### Error File
      
      Wraps segment in Error Boundary:
      
      ```tsx
      // app/error.tsx
      'use client' // Error components must be Client Components
      
      export default function Error({
        error,
        reset,
      }: {
        error: Error & { digest?: string }
        reset: () => void
      }) {
        return (
          <div>
            <h2>Something went wrong!</h2>
            <p>{error.message}</p>
            <button onClick={() => reset()}>Try again</button>
          </div>
        )
      }
      ```
      
      ### Global Error
      
      Catches errors in root layout:
      
      ```tsx
      // app/global-error.tsx
      'use client'
      
      export default function GlobalError({
        error,
        reset,
      }: {
        error: Error & { digest?: string }
        reset: () => void
      }) {
        return (
          <html>
            <body>
              <h2>Application Error!</h2>
              <button onClick={() => reset()}>Try again</button>
            </body>
          </html>
        )
      }
      ```
      
      ### Not Found
      
      ```tsx
      // app/blog/[slug]/page.tsx
      import { notFound } from 'next/navigation'
      
      export default async function Post({ params }: { params: { slug: string } }) {
        const post = await getPost(params.slug)
      
        if (!post) {
          notFound() // Triggers not-found.tsx
        }
      
        return <article>{post.content}</article>
      }
      
      // app/blog/[slug]/not-found.tsx
      export default function NotFound() {
        return <h2>Post not found</h2>
      }
      ```
      
      ## Navigation
      
      ### Link Component
      
      ```tsx
      import Link from 'next/link'
      
      // Basic link
      <Link href="/about">About</Link>
      
      // Dynamic route
      <Link href={`/blog/${post.slug}`}>Read Post</Link>
      
      // With object
      <Link href={{
        pathname: '/blog/[slug]',
        query: { slug: 'hello-world' },
      }}>
        Read Post
      </Link>
      
      // Prefetch control
      <Link href="/dashboard" prefetch={false}>
        Dashboard
      </Link>
      
      // Replace history
      <Link href="/search" replace>
        Search
      </Link>
      ```
      
      ### useRouter Hook (Client)
      
      ```tsx
      'use client'
      
      import { useRouter } from 'next/navigation'
      
      export function NavigateButton() {
        const router = useRouter()
      
        return (
          <>
            <button onClick={() => router.push('/dashboard')}>Dashboard</button>
            <button onClick={() => router.replace('/login')}>Login</button>
            <button onClick={() => router.refresh()}>Refresh</button>
            <button onClick={() => router.back()}>Back</button>
            <button onClick={() => router.forward()}>Forward</button>
          </>
        )
      }
      ```
      
      ### Programmatic Navigation (Server)
      
      ```tsx
      import { redirect } from 'next/navigation'
      
      export default async function Page() {
        const session = await getSession()
      
        if (!session) {
          redirect('/login')
        }
      
        return <div>Protected content</div>
      }
      ```
      
      ## Accessing Route Information
      
      ### searchParams (Server)
      
      ```tsx
      // app/shop/page.tsx
      export default function Shop({
        searchParams,
      }: {
        searchParams: { sort?: string; filter?: string }
      }) {
        const sort = searchParams.sort || 'newest'
        const filter = searchParams.filter
      
        return <div>Showing: {filter}, sorted by {sort}</div>
      }
      // Accessed via: /shop?sort=price&filter=shirts
      ```
      
      ### useSearchParams (Client)
      
      ```tsx
      'use client'
      
      import { useSearchParams } from 'next/navigation'
      
      export function SearchFilter() {
        const searchParams = useSearchParams()
        const query = searchParams.get('q')
      
        return <div>Search query: {query}</div>
      }
      ```
      
      ### usePathname (Client)
      
      ```tsx
      'use client'
      
      import { usePathname } from 'next/navigation'
      import Link from 'next/link'
      
      export function Navigation() {
        const pathname = usePathname()
      
        return (
          <nav>
            <Link href="/" className={pathname === '/' ? 'active' : ''}>
              Home
            </Link>
            <Link href="/about" className={pathname === '/about' ? 'active' : ''}>
              About
            </Link>
          </nav>
        )
      }
      ```
      
      ## Project Structure Best Practices
      
      ```
      app/
      ├── (auth)/                 # Route group for auth pages
      │   ├── login/
      │   ├── signup/
      │   └── layout.tsx         # Auth layout
      ├── (dashboard)/           # Route group for dashboard
      │   ├── dashboard/
      │   ├── settings/
      │   └── layout.tsx         # Dashboard layout
      ├── api/                   # API routes
      │   ├── auth/
      │   └── posts/
      ├── _components/           # Private folder (not routes)
      │   ├── header.tsx
      │   └── footer.tsx
      ├── _lib/                  # Private utilities
      │   ├── auth.ts
      │   └── db.ts
      ├── layout.tsx             # Root layout
      ├── page.tsx               # Home page
      ├── loading.tsx
      ├── error.tsx
      └── not-found.tsx
      ```
      
      Use underscore prefix for folders that shouldn't be routes.
      
    • nextjs-data-fetching.md 10 KB
      # Next.js Data Fetching
      
      Server-side data fetching, caching strategies, revalidation, and loading patterns.
      
      ## Fetch API Extensions
      
      Next.js extends native fetch with caching and revalidation:
      
      ```tsx
      // Force cache (default) - cache forever
      fetch('https://api.example.com/data', { cache: 'force-cache' })
      
      // No cache - fetch on every request
      fetch('https://api.example.com/data', { cache: 'no-store' })
      
      // Revalidate - cache with time-based revalidation
      fetch('https://api.example.com/data', { next: { revalidate: 3600 } })
      
      // Tag-based revalidation
      fetch('https://api.example.com/data', { next: { tags: ['posts'] } })
      ```
      
      ## Caching Strategies
      
      ### Static Data (Default)
      
      Fetched at build time, cached indefinitely:
      
      ```tsx
      // app/posts/page.tsx
      async function getPosts() {
        const res = await fetch('https://api.example.com/posts')
        // Same as: fetch(url, { cache: 'force-cache' })
        return res.json()
      }
      
      export default async function Posts() {
        const posts = await getPosts()
        return <PostsList posts={posts} />
      }
      ```
      
      Use for: Content that rarely changes, static pages
      
      ### Dynamic Data
      
      Fetched on every request:
      
      ```tsx
      async function getUser() {
        const res = await fetch('https://api.example.com/user', {
          cache: 'no-store'
        })
        return res.json()
      }
      
      export default async function Profile() {
        const user = await getUser()
        return <div>{user.name}</div>
      }
      ```
      
      Use for: User-specific data, real-time content
      
      ### Incremental Static Regeneration (ISR)
      
      Revalidate cached data after time period:
      
      ```tsx
      async function getPosts() {
        const res = await fetch('https://api.example.com/posts', {
          next: { revalidate: 60 } // Revalidate every 60 seconds
        })
        return res.json()
      }
      
      export default async function Posts() {
        const posts = await getPosts()
        return <PostsList posts={posts} />
      }
      ```
      
      How it works:
      1. First request: Generate page, cache it
      2. Subsequent requests: Serve cached page
      3. After 60s: Next request triggers regeneration in background
      4. New page cached, served to subsequent requests
      
      Use for: News sites, blogs, product listings
      
      ## Revalidation Strategies
      
      ### Time-Based Revalidation
      
      ```tsx
      // Revalidate every hour
      fetch('https://api.example.com/posts', {
        next: { revalidate: 3600 }
      })
      
      // Revalidate every 10 seconds
      fetch('https://api.example.com/trending', {
        next: { revalidate: 10 }
      })
      ```
      
      ### On-Demand Revalidation
      
      Revalidate specific paths or tags programmatically:
      
      ```tsx
      // app/actions.ts
      'use server'
      
      import { revalidatePath, revalidateTag } from 'next/cache'
      
      export async function createPost(formData: FormData) {
        const post = await db.post.create({
          data: {
            title: formData.get('title'),
            content: formData.get('content')
          }
        })
      
        // Revalidate specific path
        revalidatePath('/posts')
        revalidatePath(`/posts/${post.id}`)
      
        // Or revalidate by tag
        revalidateTag('posts')
      }
      ```
      
      Tag-based revalidation:
      ```tsx
      // Fetch with tags
      async function getPosts() {
        const res = await fetch('https://api.example.com/posts', {
          next: { tags: ['posts', 'content'] }
        })
        return res.json()
      }
      
      async function getComments(postId: string) {
        const res = await fetch(`https://api.example.com/comments/${postId}`, {
          next: { tags: ['comments', `post-${postId}`] }
        })
        return res.json()
      }
      
      // Revalidate all 'posts' tagged requests
      revalidateTag('posts')
      
      // Revalidate specific post comments
      revalidateTag(`post-${postId}`)
      ```
      
      ### Route Segment Config
      
      Configure entire route segment:
      
      ```tsx
      // app/posts/page.tsx
      export const revalidate = 3600 // Revalidate every hour
      
      export default async function Posts() {
        const posts = await fetch('https://api.example.com/posts').then(r => r.json())
        return <PostsList posts={posts} />
      }
      ```
      
      Options:
      ```tsx
      export const dynamic = 'auto' // default
      export const dynamic = 'force-dynamic' // no caching
      export const dynamic = 'error' // error if dynamic
      export const dynamic = 'force-static' // force static
      
      export const revalidate = false // never revalidate (default)
      export const revalidate = 0 // no cache
      export const revalidate = 60 // revalidate every 60s
      
      export const fetchCache = 'auto' // default
      export const fetchCache = 'default-cache'
      export const fetchCache = 'only-cache'
      export const fetchCache = 'force-cache'
      export const fetchCache = 'default-no-store'
      export const fetchCache = 'only-no-store'
      export const fetchCache = 'force-no-store'
      ```
      
      ## Data Fetching Patterns
      
      ### Parallel Fetching
      
      Fetch multiple resources simultaneously:
      
      ```tsx
      async function getData() {
        // Start both requests in parallel
        const [posts, users] = await Promise.all([
          fetch('https://api.example.com/posts').then(r => r.json()),
          fetch('https://api.example.com/users').then(r => r.json())
        ])
      
        return { posts, users }
      }
      
      export default async function Page() {
        const { posts, users } = await getData()
        return (
          <div>
            <PostsList posts={posts} />
            <UsersList users={users} />
          </div>
        )
      }
      ```
      
      ### Sequential Fetching
      
      Fetch dependent data:
      
      ```tsx
      async function getData(postId: string) {
        // Fetch post first
        const post = await fetch(`https://api.example.com/posts/${postId}`).then(r => r.json())
      
        // Then fetch author based on post data
        const author = await fetch(`https://api.example.com/users/${post.authorId}`).then(r => r.json())
      
        return { post, author }
      }
      
      export default async function Post({ params }: { params: { id: string } }) {
        const { post, author } = await getData(params.id)
        return (
          <article>
            <h1>{post.title}</h1>
            <p>By {author.name}</p>
            <div>{post.content}</div>
          </article>
        )
      }
      ```
      
      ### Preloading Data
      
      Optimize sequential waterfalls:
      
      ```tsx
      // lib/data.ts
      import { cache } from 'react'
      
      export const getUser = cache(async (id: string) => {
        const res = await fetch(`https://api.example.com/users/${id}`)
        return res.json()
      })
      
      // app/user/[id]/page.tsx
      import { getUser } from '@/lib/data'
      
      // Preload before component renders
      async function preload(id: string) {
        void getUser(id) // Start fetching immediately
      }
      
      export default async function User({ params }: { params: { id: string } }) {
        preload(params.id) // Start fetch
        // Render other UI
        const user = await getUser(params.id) // Will use cached result
      
        return <div>{user.name}</div>
      }
      ```
      
      ## Loading States
      
      ### Loading File
      
      Automatic loading UI:
      
      ```tsx
      // app/dashboard/loading.tsx
      export default function Loading() {
        return <div className="spinner">Loading dashboard...</div>
      }
      
      // app/dashboard/page.tsx
      export default async function Dashboard() {
        const data = await fetchDashboard()
        return <DashboardView data={data} />
      }
      ```
      
      ### Suspense Boundaries
      
      Granular loading states:
      
      ```tsx
      // app/dashboard/page.tsx
      import { Suspense } from 'react'
      
      async function Revenue() {
        const data = await fetchRevenue() // 2s
        return <RevenueChart data={data} />
      }
      
      async function Sales() {
        const data = await fetchSales() // 0.5s
        return <SalesTable data={data} />
      }
      
      export default function Dashboard() {
        return (
          <div>
            <h1>Dashboard</h1>
      
            <Suspense fallback={<RevenueChartSkeleton />}>
              <Revenue />
            </Suspense>
      
            <Suspense fallback={<SalesTableSkeleton />}>
              <Sales />
            </Suspense>
          </div>
        )
      }
      ```
      
      Sales loads after 0.5s, Revenue after 2s - no blocking.
      
      ## Static Generation
      
      ### generateStaticParams
      
      Pre-render dynamic routes at build time:
      
      ```tsx
      // app/posts/[slug]/page.tsx
      export async function generateStaticParams() {
        const posts = await fetch('https://api.example.com/posts').then(r => r.json())
      
        return posts.map(post => ({
          slug: post.slug
        }))
      }
      
      export default async function Post({ params }: { params: { slug: string } }) {
        const post = await fetch(`https://api.example.com/posts/${params.slug}`).then(r => r.json())
        return <article>{post.content}</article>
      }
      ```
      
      Generates static pages at build:
      - `/posts/hello-world`
      - `/posts/nextjs-guide`
      - `/posts/react-tips`
      
      ### Dynamic Params Handling
      
      ```tsx
      // app/posts/[slug]/page.tsx
      export const dynamicParams = true // default - generate on-demand if not pre-rendered
      
      export const dynamicParams = false // 404 for paths not in generateStaticParams
      ```
      
      ## Error Handling
      
      ### Try-Catch in Server Components
      
      ```tsx
      async function getData() {
        try {
          const res = await fetch('https://api.example.com/data')
      
          if (!res.ok) {
            throw new Error('Failed to fetch data')
          }
      
          return res.json()
        } catch (error) {
          console.error('Data fetch error:', error)
          return null
        }
      }
      
      export default async function Page() {
        const data = await getData()
      
        if (!data) {
          return <div>Failed to load data</div>
        }
      
        return <DataView data={data} />
      }
      ```
      
      ### Error Boundaries
      
      ```tsx
      // 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>
        )
      }
      ```
      
      ## Database Queries
      
      Direct database access in Server Components:
      
      ```tsx
      // lib/db.ts
      import { PrismaClient } from '@prisma/client'
      
      const prisma = new PrismaClient()
      
      export async function getPosts() {
        return prisma.post.findMany({
          include: { author: true },
          orderBy: { createdAt: 'desc' }
        })
      }
      
      // app/posts/page.tsx
      import { getPosts } from '@/lib/db'
      
      export default async function Posts() {
        const posts = await getPosts()
        return <PostsList posts={posts} />
      }
      ```
      
      ## Best Practices
      
      1. **Default to static** - Use `cache: 'force-cache'` or default behavior
      2. **Use ISR for semi-dynamic content** - Balance freshness and performance
      3. **Fetch in parallel** - Use `Promise.all()` for independent requests
      4. **Add loading states** - Use Suspense for better UX
      5. **Handle errors gracefully** - Provide fallbacks and error boundaries
      6. **Use on-demand revalidation** - Trigger updates after mutations
      7. **Tag your fetches** - Enable granular cache invalidation
      8. **Dedupe automatically** - Next.js dedupes identical fetch requests
      9. **Avoid client-side fetching** - Use Server Components when possible
      10. **Cache database queries** - Use React cache() for expensive queries
      
    • nextjs-optimization.md 10.9 KB
      # Next.js Optimization
      
      Performance optimization techniques for images, fonts, scripts, and bundles.
      
      ## Image Optimization
      
      ### Next.js Image Component
      
      Automatic optimization with modern formats (WebP, AVIF):
      
      ```tsx
      import Image from 'next/image'
      
      export default function Page() {
        return (
          <>
            {/* Local image */}
            <Image
              src="/hero.jpg"
              alt="Hero image"
              width={1200}
              height={600}
              priority // Load immediately, no lazy loading
            />
      
            {/* Remote image */}
            <Image
              src="https://example.com/photo.jpg"
              alt="Photo"
              width={800}
              height={600}
              quality={90} // 1-100, default 75
            />
      
            {/* Responsive fill */}
            <div style={{ position: 'relative', width: '100%', height: '400px' }}>
              <Image
                src="/background.jpg"
                alt="Background"
                fill
                style={{ objectFit: 'cover' }}
                sizes="100vw"
              />
            </div>
      
            {/* With blur placeholder */}
            <Image
              src="/profile.jpg"
              alt="Profile"
              width={200}
              height={200}
              placeholder="blur"
              blurDataURL="data:image/jpeg;base64,..." // Or use static import
            />
          </>
        )
      }
      ```
      
      ### Image Props Reference
      
      **Required:**
      - `src` - Image path (string or static import)
      - `alt` - Alt text for accessibility
      - `width`, `height` - Dimensions (required unless using `fill`)
      
      **Optional:**
      - `fill` - Fill parent container (makes width/height optional)
      - `sizes` - Responsive sizes hint for srcset
      - `quality` - 1-100 (default 75)
      - `priority` - Disable lazy loading, preload image
      - `placeholder` - 'blur' | 'empty' (default 'empty')
      - `blurDataURL` - Data URL for blur placeholder
      - `loading` - 'lazy' | 'eager' (default 'lazy')
      - `style` - CSS styles
      - `className` - CSS class
      - `onLoad` - Callback when loaded
      
      ### Responsive Images with Sizes
      
      ```tsx
      <Image
        src="/hero.jpg"
        alt="Hero"
        fill
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
      />
      ```
      
      This tells browser:
      - Mobile (<768px): Use 100% viewport width
      - Tablet (768-1200px): Use 50% viewport width
      - Desktop (>1200px): Use 33% viewport width
      
      ### Static Import for Local Images
      
      ```tsx
      import heroImage from '@/public/hero.jpg'
      
      <Image
        src={heroImage}
        alt="Hero"
        placeholder="blur" // Automatically generated
        // No width/height needed - inferred from import
      />
      ```
      
      ### Remote Image Configuration
      
      ```js
      // next.config.js
      module.exports = {
        images: {
          remotePatterns: [
            {
              protocol: 'https',
              hostname: 'example.com',
              pathname: '/images/**',
            },
            {
              protocol: 'https',
              hostname: 'cdn.example.com',
            }
          ],
          // Device sizes for srcset
          deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
          // Image sizes for srcset
          imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
          // Supported formats
          formats: ['image/webp'],
          // Cache optimization images for 60 days
          minimumCacheTTL: 60 * 60 * 24 * 60,
        }
      }
      ```
      
      ## Font Optimization
      
      ### Google Fonts
      
      Automatic optimization with zero layout shift:
      
      ```tsx
      // app/layout.tsx
      import { Inter, Roboto_Mono, Playfair_Display } from 'next/font/google'
      
      const inter = Inter({
        subsets: ['latin'],
        display: 'swap',
        variable: '--font-inter',
      })
      
      const robotoMono = Roboto_Mono({
        subsets: ['latin'],
        display: 'swap',
        weight: ['400', '700'],
        variable: '--font-roboto-mono',
      })
      
      const playfair = Playfair_Display({
        subsets: ['latin'],
        display: 'swap',
        weight: ['400', '700', '900'],
        style: ['normal', 'italic'],
      })
      
      export default function RootLayout({ children }) {
        return (
          <html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
            <body className={inter.className}>{children}</body>
          </html>
        )
      }
      ```
      
      Use CSS variables:
      ```css
      .code {
        font-family: var(--font-roboto-mono);
      }
      ```
      
      ### Local Fonts
      
      ```tsx
      import localFont from 'next/font/local'
      
      const myFont = localFont({
        src: [
          {
            path: './fonts/my-font-regular.woff2',
            weight: '400',
            style: 'normal',
          },
          {
            path: './fonts/my-font-bold.woff2',
            weight: '700',
            style: 'normal',
          }
        ],
        variable: '--font-my-font',
        display: 'swap',
      })
      
      export default function RootLayout({ children }) {
        return (
          <html lang="en" className={myFont.variable}>
            <body>{children}</body>
          </html>
        )
      }
      ```
      
      ### Font Display Strategies
      
      ```tsx
      const font = Inter({
        display: 'swap', // Show fallback immediately, swap when loaded (recommended)
        // display: 'optional', // Only use font if available immediately
        // display: 'block', // Hide text until font loads (max 3s)
        // display: 'fallback', // Show fallback briefly, swap if loaded quickly
        // display: 'auto', // Browser default
      })
      ```
      
      ## Script Optimization
      
      ### Script Component
      
      Control loading behavior:
      
      ```tsx
      import Script from 'next/script'
      
      export default function Page() {
        return (
          <>
            {/* Load after page is interactive (recommended for analytics) */}
            <Script
              src="https://www.googletagmanager.com/gtag/js"
              strategy="afterInteractive"
            />
      
            {/* Load while page is idle (lowest priority) */}
            <Script
              src="https://connect.facebook.net/en_US/sdk.js"
              strategy="lazyOnload"
            />
      
            {/* Load before page is interactive (use sparingly) */}
            <Script
              src="https://maps.googleapis.com/maps/api/js"
              strategy="beforeInteractive"
            />
      
            {/* Inline script with strategy */}
            <Script id="analytics" strategy="afterInteractive">
              {`
                window.dataLayer = window.dataLayer || [];
                function gtag(){dataLayer.push(arguments);}
                gtag('js', new Date());
              `}
            </Script>
      
            {/* With onLoad callback */}
            <Script
              src="https://example.com/sdk.js"
              onLoad={() => console.log('Script loaded')}
              onError={(e) => console.error('Script failed', e)}
            />
          </>
        )
      }
      ```
      
      **Strategy options:**
      - `beforeInteractive` - Load before page interactive (blocking)
      - `afterInteractive` - Load after page interactive (default)
      - `lazyOnload` - Load during idle time
      - `worker` - Load in web worker (experimental)
      
      ## Bundle Optimization
      
      ### Analyzing Bundle Size
      
      ```bash
      # Install bundle analyzer
      npm install @next/bundle-analyzer
      
      # Create next.config.js wrapper
      ```
      
      ```js
      // next.config.js
      const withBundleAnalyzer = require('@next/bundle-analyzer')({
        enabled: process.env.ANALYZE === 'true',
      })
      
      module.exports = withBundleAnalyzer({
        // Your Next.js config
      })
      ```
      
      ```bash
      # Run analysis
      ANALYZE=true npm run build
      ```
      
      ### Dynamic Import (Code Splitting)
      
      Split code and load on-demand:
      
      ```tsx
      import dynamic from 'next/dynamic'
      
      // Dynamic import with loading state
      const DynamicChart = dynamic(() => import('@/components/chart'), {
        loading: () => <div>Loading chart...</div>,
        ssr: false, // Disable SSR for this component
      })
      
      export default function Dashboard() {
        return (
          <div>
            <h1>Dashboard</h1>
            <DynamicChart />
          </div>
        )
      }
      ```
      
      Named exports:
      ```tsx
      const DynamicComponent = dynamic(
        () => import('@/components/hello').then(mod => mod.Hello)
      )
      ```
      
      Multiple components:
      ```tsx
      const DynamicHeader = dynamic(() => import('@/components/header'))
      const DynamicFooter = dynamic(() => import('@/components/footer'))
      ```
      
      ### Tree Shaking
      
      Import only what you need:
      
      ```tsx
      // ❌ Bad - imports entire library
      import _ from 'lodash'
      const result = _.debounce(fn, 300)
      
      // ✅ Good - imports only debounce
      import debounce from 'lodash/debounce'
      const result = debounce(fn, 300)
      
      // ❌ Bad
      import * as Icons from 'react-icons/fa'
      <Icons.FaHome />
      
      // ✅ Good
      import { FaHome } from 'react-icons/fa'
      <FaHome />
      ```
      
      ## Partial Prerendering (PPR)
      
      Experimental: Combine static and dynamic rendering in same route.
      
      ```js
      // next.config.js
      module.exports = {
        experimental: {
          ppr: true,
        }
      }
      ```
      
      ```tsx
      // app/page.tsx
      import { Suspense } from 'react'
      
      // Static shell
      export default function Page() {
        return (
          <div>
            <header>Static Header</header>
      
            {/* Dynamic content with Suspense boundary */}
            <Suspense fallback={<div>Loading...</div>}>
              <DynamicContent />
            </Suspense>
      
            <footer>Static Footer</footer>
          </div>
        )
      }
      
      // Dynamic component
      async function DynamicContent() {
        const data = await fetch('https://api.example.com/data', {
          cache: 'no-store'
        }).then(r => r.json())
      
        return <div>{data.content}</div>
      }
      ```
      
      Static shell loads instantly, dynamic content streams in.
      
      ## Metadata Optimization
      
      ### Static Metadata
      
      ```tsx
      // app/page.tsx
      import { Metadata } from 'next'
      
      export const metadata: Metadata = {
        title: 'My Page',
        description: 'Page description',
        keywords: ['next.js', 'react', 'javascript'],
        openGraph: {
          title: 'My Page',
          description: 'Page description',
          images: ['/og-image.jpg'],
        },
        twitter: {
          card: 'summary_large_image',
          title: 'My Page',
          description: 'Page description',
          images: ['/twitter-image.jpg'],
        },
        alternates: {
          canonical: 'https://example.com/page',
        },
        robots: {
          index: true,
          follow: true,
        },
      }
      ```
      
      ### Dynamic Metadata
      
      ```tsx
      // app/blog/[slug]/page.tsx
      export async function generateMetadata({ params }): Promise<Metadata> {
        const post = await getPost(params.slug)
      
        return {
          title: post.title,
          description: post.excerpt,
          openGraph: {
            title: post.title,
            description: post.excerpt,
            images: [post.coverImage],
            type: 'article',
            publishedTime: post.publishedAt,
            authors: [post.author.name],
          },
        }
      }
      ```
      
      ### Metadata Files
      
      Create these files in `app/` directory:
      
      - `favicon.ico` - Favicon
      - `icon.png` / `icon.jpg` - App icon
      - `apple-icon.png` - Apple touch icon
      - `opengraph-image.png` - Open Graph image
      - `twitter-image.png` - Twitter card image
      - `robots.txt` - Robots file
      - `sitemap.xml` - Sitemap
      
      Or generate dynamically:
      ```tsx
      // app/sitemap.ts
      export default async function sitemap() {
        const posts = await getPosts()
      
        return [
          {
            url: 'https://example.com',
            lastModified: new Date(),
          },
          ...posts.map(post => ({
            url: `https://example.com/blog/${post.slug}`,
            lastModified: post.updatedAt,
          }))
        ]
      }
      ```
      
      ## Performance Best Practices
      
      1. **Use Image component** - Automatic optimization, lazy loading, modern formats
      2. **Optimize fonts** - Use next/font to eliminate layout shift
      3. **Dynamic imports** - Code split large components and third-party libraries
      4. **Analyze bundle** - Identify and eliminate large dependencies
      5. **Proper caching** - Use ISR for semi-static content
      6. **Streaming with Suspense** - Load fast content first, stream slow content
      7. **Minimize JavaScript** - Default to Server Components
      8. **Prefetch links** - Next.js prefetches Link components in viewport
      9. **Use Script component** - Control third-party script loading
      10. **Compress assets** - Enable compression in hosting platform
      11. **Use CDN** - Deploy to edge network (Vercel, Cloudflare)
      12. **Monitor metrics** - Track Core Web Vitals (LCP, FID, CLS)
      
    • nextjs-server-components.md 10.5 KB
      # Next.js Server Components
      
      React Server Components (RSC) architecture, patterns, and best practices.
      
      ## Core Concepts
      
      ### Server Components (Default)
      
      All components in `app/` directory are Server Components by default:
      
      ```tsx
      // app/posts/page.tsx - Server Component
      async function getPosts() {
        const res = await fetch('https://api.example.com/posts')
        return res.json()
      }
      
      export default async function PostsPage() {
        const posts = await getPosts()
      
        return (
          <div>
            {posts.map(post => (
              <article key={post.id}>{post.title}</article>
            ))}
          </div>
        )
      }
      ```
      
      **Benefits:**
      - Fetch data on server (direct database access)
      - Keep sensitive data/keys on server
      - Reduce client-side JavaScript bundle
      - Improve initial page load and SEO
      - Cache results on server
      - Stream content to client
      
      **Limitations:**
      - Cannot use React hooks (useState, useEffect, useContext)
      - Cannot use browser APIs (window, localStorage)
      - Cannot add event listeners (onClick, onChange)
      - Cannot use React class components
      
      ### Client Components
      
      Mark with `'use client'` directive at top of file:
      
      ```tsx
      // components/counter.tsx - Client Component
      'use client'
      
      import { useState } from 'react'
      
      export function Counter() {
        const [count, setCount] = useState(0)
      
        return (
          <button onClick={() => setCount(count + 1)}>
            Count: {count}
          </button>
        )
      }
      ```
      
      **Use Client Components for:**
      - Interactive UI (event handlers)
      - State management (useState, useReducer)
      - Effects (useEffect, useLayoutEffect)
      - Browser-only APIs (localStorage, geolocation)
      - Custom React hooks
      - Context consumers
      
      ## Composition Patterns
      
      ### Server Component as Wrapper
      
      Best practice: Keep Server Components as parent, pass Client Components as children:
      
      ```tsx
      // app/page.tsx - Server Component
      import { ClientSidebar } from './sidebar'
      import { ClientButton } from './button'
      
      export default async function Page() {
        const data = await fetchData() // Server-side data fetch
      
        return (
          <div>
            <h1>Server-rendered heading</h1>
            <ClientSidebar />
            <ClientButton />
            <p>More server-rendered content: {data.title}</p>
          </div>
        )
      }
      ```
      
      ### Passing Server Components to Client Components
      
      Use children pattern to avoid making entire tree client-side:
      
      ```tsx
      // app/page.tsx - Server Component
      import { ClientProvider } from './client-provider'
      import { ServerContent } from './server-content'
      
      export default function Page() {
        return (
          <ClientProvider>
            <ServerContent /> {/* Stays as Server Component */}
          </ClientProvider>
        )
      }
      
      // client-provider.tsx - Client Component
      'use client'
      
      export function ClientProvider({ children }: { children: React.ReactNode }) {
        const [state, setState] = useState()
      
        return <div>{children}</div>
      }
      
      // server-content.tsx - Server Component
      export async function ServerContent() {
        const data = await fetchData()
        return <p>{data.content}</p>
      }
      ```
      
      ### Sharing Data Between Server Components
      
      No need for props or context - just fetch data where needed:
      
      ```tsx
      // lib/data.ts
      export async function getUser() {
        const res = await fetch('https://api.example.com/user', {
          cache: 'force-cache' // Will dedupe automatically
        })
        return res.json()
      }
      
      // app/header.tsx
      import { getUser } from '@/lib/data'
      
      export async function Header() {
        const user = await getUser() // Fetch 1
        return <div>Welcome, {user.name}</div>
      }
      
      // app/profile.tsx
      import { getUser } from '@/lib/data'
      
      export async function Profile() {
        const user = await getUser() // Fetch 2 (deduped automatically)
        return <div>Email: {user.email}</div>
      }
      ```
      
      Next.js automatically dedupes identical fetch requests during render.
      
      ## Async Components
      
      Server Components can be async functions:
      
      ```tsx
      // app/posts/[id]/page.tsx
      async function getPost(id: string) {
        const res = await fetch(`https://api.example.com/posts/${id}`)
        return res.json()
      }
      
      async function getComments(postId: string) {
        const res = await fetch(`https://api.example.com/posts/${postId}/comments`)
        return res.json()
      }
      
      export default async function Post({ params }: { params: { id: string } }) {
        // Parallel data fetching
        const [post, comments] = await Promise.all([
          getPost(params.id),
          getComments(params.id)
        ])
      
        return (
          <article>
            <h1>{post.title}</h1>
            <p>{post.content}</p>
            <CommentsList comments={comments} />
          </article>
        )
      }
      ```
      
      ## Streaming with Suspense
      
      Stream components as they resolve:
      
      ```tsx
      // app/page.tsx
      import { Suspense } from 'react'
      
      async function SlowComponent() {
        await new Promise(resolve => setTimeout(resolve, 3000))
        return <div>Loaded after 3 seconds</div>
      }
      
      async function FastComponent() {
        await new Promise(resolve => setTimeout(resolve, 500))
        return <div>Loaded after 0.5 seconds</div>
      }
      
      export default function Page() {
        return (
          <div>
            <h1>Instant heading</h1>
      
            <Suspense fallback={<div>Loading fast...</div>}>
              <FastComponent />
            </Suspense>
      
            <Suspense fallback={<div>Loading slow...</div>}>
              <SlowComponent />
            </Suspense>
          </div>
        )
      }
      ```
      
      Benefits:
      - Fast components render immediately
      - Slow components don't block page
      - Progressive enhancement
      - Better perceived performance
      
      ## Context in Server/Client Components
      
      ### Problem: Context Requires Client Components
      
      ```tsx
      // ❌ Won't work - Server Components can't use context
      import { createContext } from 'react'
      
      const ThemeContext = createContext()
      
      export default function Layout({ children }) {
        return (
          <ThemeContext.Provider value="dark">
            {children}
          </ThemeContext.Provider>
        )
      }
      ```
      
      ### Solution: Create Client Component Wrapper
      
      ```tsx
      // app/providers.tsx - Client Component
      'use client'
      
      import { createContext, useContext } from 'react'
      
      const ThemeContext = createContext('light')
      
      export function ThemeProvider({ children }: { children: React.ReactNode }) {
        return (
          <ThemeContext.Provider value="dark">
            {children}
          </ThemeContext.Provider>
        )
      }
      
      export function useTheme() {
        return useContext(ThemeContext)
      }
      
      // app/layout.tsx - Server Component
      import { ThemeProvider } from './providers'
      
      export default function RootLayout({ children }) {
        return (
          <html>
            <body>
              <ThemeProvider>
                {children}
              </ThemeProvider>
            </body>
          </html>
        )
      }
      ```
      
      ## Third-Party Component Integration
      
      Many third-party components need client-side features:
      
      ```tsx
      // components/carousel.tsx
      'use client'
      
      import 'slick-carousel/slick/slick.css'
      import Slider from 'react-slick'
      
      export function Carousel({ children }) {
        return <Slider>{children}</Slider>
      }
      
      // app/page.tsx - Server Component
      import { Carousel } from '@/components/carousel'
      
      export default function Page() {
        return (
          <Carousel>
            <div>Slide 1</div>
            <div>Slide 2</div>
          </Carousel>
        )
      }
      ```
      
      ## Server Actions
      
      Call server-side functions from Client Components:
      
      ```tsx
      // app/actions.ts
      'use server'
      
      import { revalidatePath } from 'next/cache'
      import { db } from '@/lib/db'
      
      export async function createPost(formData: FormData) {
        const title = formData.get('title') as string
        const content = formData.get('content') as string
      
        await db.post.create({
          data: { title, content }
        })
      
        revalidatePath('/posts')
      }
      
      // app/new-post/page.tsx
      import { createPost } from '@/app/actions'
      
      export default function NewPost() {
        return (
          <form action={createPost}>
            <input name="title" required />
            <textarea name="content" required />
            <button type="submit">Create Post</button>
          </form>
        )
      }
      ```
      
      With Client Component:
      ```tsx
      // components/post-form.tsx
      'use client'
      
      import { createPost } from '@/app/actions'
      import { useFormStatus } from 'react-dom'
      
      function SubmitButton() {
        const { pending } = useFormStatus()
        return (
          <button type="submit" disabled={pending}>
            {pending ? 'Creating...' : 'Create Post'}
          </button>
        )
      }
      
      export function PostForm() {
        return (
          <form action={createPost}>
            <input name="title" required />
            <textarea name="content" required />
            <SubmitButton />
          </form>
        )
      }
      ```
      
      ## When to Use Each Component Type
      
      ### Use Server Components When:
      - Fetching data from database or API
      - Accessing backend resources directly
      - Keeping sensitive information on server (tokens, keys)
      - Reducing client-side JavaScript
      - Rendering static content
      - No interactivity needed
      
      ### Use Client Components When:
      - Adding interactivity (onClick, onChange)
      - Managing state (useState, useReducer)
      - Using lifecycle effects (useEffect)
      - Using browser-only APIs (localStorage, navigator)
      - Using custom React hooks
      - Using React Context
      - Using third-party libraries requiring client features
      
      ## Best Practices
      
      1. **Default to Server Components** - Only use 'use client' when needed
      2. **Move Client Components to leaves** - Keep them as deep as possible in tree
      3. **Pass Server Components as children** - Avoid turning entire trees client-side
      4. **Share data via fetch** - Let Next.js dedupe requests automatically
      5. **Use Suspense for streaming** - Improve perceived performance
      6. **Separate client logic** - Extract client-only code to separate files
      7. **Minimize client bundle** - Less JavaScript = faster page loads
      
      ## Common Patterns
      
      ### Protected Content
      
      ```tsx
      // app/dashboard/page.tsx - Server Component
      import { redirect } from 'next/navigation'
      import { getUser } from '@/lib/auth'
      
      export default async function Dashboard() {
        const user = await getUser()
      
        if (!user) {
          redirect('/login')
        }
      
        return <div>Welcome, {user.name}</div>
      }
      ```
      
      ### Optimistic Updates
      
      ```tsx
      // components/like-button.tsx
      'use client'
      
      import { useOptimistic } from 'react'
      import { likePost } from '@/app/actions'
      
      export function LikeButton({ postId, initialLikes }) {
        const [optimisticLikes, addOptimisticLike] = useOptimistic(
          initialLikes,
          (state, amount) => state + amount
        )
      
        return (
          <button
            onClick={async () => {
              addOptimisticLike(1)
              await likePost(postId)
            }}
          >
            Likes: {optimisticLikes}
          </button>
        )
      }
      ```
      
      ### Loading States with Streaming
      
      ```tsx
      // app/dashboard/page.tsx
      import { Suspense } from 'react'
      
      async function RevenueChart() {
        const data = await fetchRevenue() // Slow query
        return <Chart data={data} />
      }
      
      async function RecentSales() {
        const sales = await fetchSales() // Fast query
        return <SalesTable sales={sales} />
      }
      
      export default function Dashboard() {
        return (
          <div>
            <h1>Dashboard</h1>
      
            <Suspense fallback={<ChartSkeleton />}>
              <RevenueChart />
            </Suspense>
      
            <Suspense fallback={<TableSkeleton />}>
              <RecentSales />
            </Suspense>
          </div>
        )
      }
      ```
      
    • remix-icon-integration.md 10.6 KB
      # RemixIcon Integration Guide
      
      Installation, usage, customization, and accessibility for RemixIcon library.
      
      ## Overview
      
      RemixIcon provides 3,100+ icons in outlined (-line) and filled (-fill) styles, built on 24x24px grid.
      
      **Icon naming:** `ri-{name}-{style}`
      - Examples: `ri-home-line`, `ri-heart-fill`, `ri-search-line`
      
      ## Installation
      
      ### NPM Package
      
      ```bash
      # npm
      npm install remixicon
      
      # yarn
      yarn add remixicon
      
      # pnpm
      pnpm install remixicon
      
      # bun
      bun add remixicon
      ```
      
      ### React Package
      
      ```bash
      npm install @remixicon/react
      ```
      
      ### Vue 3 Package
      
      ```bash
      npm install @remixicon/vue
      ```
      
      ### CDN
      
      ```html
      <link
        href="https://cdn.jsdelivr.net/npm/remixicon@4.7.0/fonts/remixicon.css"
        rel="stylesheet"
      />
      ```
      
      ## Usage Methods
      
      ### 1. Webfont (HTML/CSS)
      
      Import CSS and use class names:
      
      ```tsx
      // Next.js - app/layout.tsx
      import 'remixicon/fonts/remixicon.css'
      
      export default function RootLayout({ children }) {
        return (
          <html>
            <body>{children}</body>
          </html>
        )
      }
      
      // Use in components
      <i className="ri-home-line"></i>
      <i className="ri-search-fill"></i>
      ```
      
      **With sizing classes:**
      ```html
      <i className="ri-home-line ri-2x"></i>    <!-- 2em -->
      <i className="ri-search-line ri-lg"></i>  <!-- 1.33em -->
      <i className="ri-heart-fill ri-xl"></i>   <!-- 1.5em -->
      ```
      
      **Available sizes:**
      - `ri-xxs` (0.5em)
      - `ri-xs` (0.75em)
      - `ri-sm` (0.875em)
      - `ri-1x` (1em)
      - `ri-lg` (1.33em)
      - `ri-xl` (1.5em)
      - `ri-2x` through `ri-10x`
      - `ri-fw` (fixed width)
      
      ### 2. React Components
      
      ```tsx
      import { RiHomeLine, RiSearchFill, RiHeartLine } from "@remixicon/react"
      
      export function MyComponent() {
        return (
          <div>
            <RiHomeLine size={24} />
            <RiSearchFill size={32} color="blue" />
            <RiHeartLine size="1.5em" className="icon" />
          </div>
        )
      }
      ```
      
      **Props:**
      - `size` - Number (pixels) or string (em, rem)
      - `color` - CSS color value
      - `className` - CSS class
      - Standard SVG props (onClick, style, etc.)
      
      ### 3. Vue 3 Components
      
      ```vue
      <script setup lang="ts">
      import { RiHomeLine, RiSearchFill } from "@remixicon/vue"
      </script>
      
      <template>
        <div>
          <RiHomeLine :size="24" />
          <RiSearchFill :size="32" color="blue" />
        </div>
      </template>
      ```
      
      ### 4. Direct SVG
      
      ```tsx
      // Download SVG file and import
      import HomeIcon from '@/icons/home-line.svg'
      
      export function Component() {
        return <img src={HomeIcon} alt="Home" width={24} height={24} />
      }
      ```
      
      ### 5. SVG Sprite
      
      ```html
      <svg className="icon">
        <use xlinkHref="path/to/remixicon.symbol.svg#ri-home-line"></use>
      </svg>
      ```
      
      ```css
      .icon {
        width: 24px;
        height: 24px;
        fill: currentColor;
      }
      ```
      
      ## Icon Categories
      
      20 semantic categories with 3,100+ icons:
      
      **Navigation & UI:**
      - Arrows (arrow-left, arrow-right, arrow-up-down)
      - System (settings, delete, add, close, more)
      - Editor (bold, italic, link, list, code)
      
      **Communication:**
      - Communication (chat, phone, mail, message)
      - User (user, account, team, contacts)
      
      **Media & Content:**
      - Media (play, pause, volume, camera, video)
      - Document (file, folder, article, draft)
      - Design (brush, palette, magic, crop)
      
      **Business & Commerce:**
      - Business (briefcase, pie-chart, bar-chart)
      - Finance (money, wallet, bank-card, coin)
      - Map (map, pin, compass, navigation)
      
      **Objects & Places:**
      - Buildings (home, bank, hospital, store)
      - Device (phone, laptop, tablet, printer)
      - Food (restaurant, cake, cup, knife)
      - Weather (sun, cloud, rain, moon)
      
      **Development & Logos:**
      - Development (code, terminal, bug, git-branch)
      - Logos (github, twitter, facebook, google)
      
      **Health & Medical:**
      - Health (heart-pulse, capsule, stethoscope)
      
      ## Common Patterns
      
      ### Navigation Menu
      
      ```tsx
      // Webfont approach
      export function Navigation() {
        return (
          <nav>
            <a href="/home">
              <i className="ri-home-line"></i>
              <span>Home</span>
            </a>
            <a href="/search">
              <i className="ri-search-line"></i>
              <span>Search</span>
            </a>
            <a href="/profile">
              <i className="ri-user-line"></i>
              <span>Profile</span>
            </a>
          </nav>
        )
      }
      
      // React component approach
      import { RiHomeLine, RiSearchLine, RiUserLine } from "@remixicon/react"
      
      export function Navigation() {
        return (
          <nav>
            <a href="/home">
              <RiHomeLine size={20} />
              <span>Home</span>
            </a>
            <a href="/search">
              <RiSearchLine size={20} />
              <span>Search</span>
            </a>
            <a href="/profile">
              <RiUserLine size={20} />
              <span>Profile</span>
            </a>
          </nav>
        )
      }
      ```
      
      ### Button with Icon
      
      ```tsx
      import { RiDownloadLine } from "@remixicon/react"
      
      export function DownloadButton() {
        return (
          <button className="btn-primary">
            <RiDownloadLine size={18} />
            <span>Download</span>
          </button>
        )
      }
      ```
      
      ### Status Indicators
      
      ```tsx
      import {
        RiCheckboxCircleFill,
        RiErrorWarningFill,
        RiAlertFill,
        RiInformationFill
      } from "@remixicon/react"
      
      type Status = 'success' | 'error' | 'warning' | 'info'
      
      export function StatusIcon({ status }: { status: Status }) {
        const icons = {
          success: <RiCheckboxCircleFill color="green" size={20} />,
          error: <RiErrorWarningFill color="red" size={20} />,
          warning: <RiAlertFill color="orange" size={20} />,
          info: <RiInformationFill color="blue" size={20} />
        }
      
        return icons[status]
      }
      ```
      
      ### Input with Icon
      
      ```tsx
      import { RiSearchLine } from "@remixicon/react"
      
      export function SearchInput() {
        return (
          <div className="input-group">
            <RiSearchLine size={20} className="input-icon" />
            <input type="text" placeholder="Search..." />
          </div>
        )
      }
      ```
      
      ```css
      .input-group {
        position: relative;
      }
      
      .input-icon {
        position: absolute;
        left: 12px;
        top: 50%;
        transform: translateY(-50%);
        color: #666;
      }
      
      input {
        padding-left: 40px;
      }
      ```
      
      ### Dynamic Icon Selection
      
      ```tsx
      import { RiHomeLine, RiHeartFill, RiStarLine } from "@remixicon/react"
      
      const iconMap = {
        home: RiHomeLine,
        heart: RiHeartFill,
        star: RiStarLine,
      }
      
      export function DynamicIcon({ name, size = 24 }: { name: string; size?: number }) {
        const Icon = iconMap[name]
        return Icon ? <Icon size={size} /> : null
      }
      
      // Usage
      <DynamicIcon name="home" size={24} />
      ```
      
      ## Styling & Customization
      
      ### Color
      
      ```tsx
      // Inherit from parent
      <i className="ri-home-line" style={{ color: 'blue' }}></i>
      
      // React component
      <RiHomeLine color="blue" />
      <RiHomeLine color="#ff0000" />
      <RiHomeLine color="rgb(255, 0, 0)" />
      ```
      
      ### Size
      
      ```tsx
      // CSS class
      <i className="ri-home-line ri-2x"></i>
      
      // Inline style
      <i className="ri-home-line" style={{ fontSize: '32px' }}></i>
      
      // React component
      <RiHomeLine size={32} />
      <RiHomeLine size="2em" />
      ```
      
      ### Responsive Sizing
      
      ```css
      .icon {
        font-size: 24px;
      }
      
      @media (max-width: 768px) {
        .icon {
          font-size: 20px;
        }
      }
      ```
      
      ### Animations
      
      ```css
      .spin {
        animation: spin 1s linear infinite;
      }
      
      @keyframes spin {
        from { transform: rotate(0deg); }
        to { transform: rotate(360deg); }
      }
      ```
      
      ```tsx
      <i className="ri-loader-4-line spin"></i>
      ```
      
      ### Hover Effects
      
      ```css
      .icon-button {
        transition: color 0.2s;
      }
      
      .icon-button:hover {
        color: #007bff;
      }
      ```
      
      ## Accessibility
      
      ### Provide Labels
      
      **Icon-only buttons:**
      ```tsx
      <button aria-label="Search">
        <i className="ri-search-line"></i>
      </button>
      
      // Or with React
      <button aria-label="Search">
        <RiSearchLine size={20} />
      </button>
      ```
      
      ### Decorative Icons
      
      Hide from screen readers:
      
      ```tsx
      <span aria-hidden="true">
        <i className="ri-star-fill"></i>
      </span>
      
      // React
      <span aria-hidden="true">
        <RiStarFill size={16} />
      </span>
      ```
      
      ### Icon with Text
      
      ```tsx
      <button>
        <RiDownloadLine size={18} aria-hidden="true" />
        <span>Download</span>
      </button>
      ```
      
      Text provides context, icon is decorative.
      
      ## Framework Integration
      
      ### Next.js
      
      ```tsx
      // app/layout.tsx
      import 'remixicon/fonts/remixicon.css'
      
      export default function RootLayout({ children }) {
        return (
          <html>
            <body>{children}</body>
          </html>
        )
      }
      
      // app/page.tsx
      import { RiHomeLine } from "@remixicon/react"
      
      export default function Page() {
        return <RiHomeLine size={24} />
      }
      ```
      
      ### Tailwind CSS
      
      ```tsx
      <i className="ri-home-line text-2xl text-blue-500"></i>
      
      <RiHomeLine size={24} className="text-blue-500 hover:text-blue-600" />
      ```
      
      ### CSS Modules
      
      ```tsx
      import styles from './component.module.css'
      import 'remixicon/fonts/remixicon.css'
      
      export function Component() {
        return <i className={`ri-home-line ${styles.icon}`}></i>
      }
      ```
      
      ## Performance Considerations
      
      ### Webfont (Recommended for Multiple Icons)
      
      **Pros:**
      - Single HTTP request
      - All icons available
      - Easy to use
      
      **Cons:**
      - 179KB WOFF2 file
      - Loads all icons even if unused
      
      **Best for:** Apps using 10+ different icons
      
      ### Individual SVG (Recommended for Few Icons)
      
      **Pros:**
      - Only load what you need
      - Smallest bundle size
      - Tree-shakeable with React package
      
      **Cons:**
      - Multiple imports
      
      **Best for:** Apps using 1-5 icons
      
      ### React/Vue Package
      
      **Pros:**
      - Tree-shakeable (only imports used icons)
      - TypeScript support
      - Component API
      
      **Cons:**
      - Slightly larger than raw SVG
      - Requires React/Vue
      
      **Best for:** React/Vue apps with TypeScript
      
      ## Troubleshooting
      
      ### Icons Not Displaying
      
      **Check CSS import:**
      ```tsx
      import 'remixicon/fonts/remixicon.css'
      ```
      
      **Verify class name:**
      ```html
      <!-- Correct -->
      <i className="ri-home-line"></i>
      
      <!-- Incorrect -->
      <i className="ri-home"></i>
      <i className="home-line"></i>
      ```
      
      **Check font loading:**
      ```css
      /* Ensure font-family is applied */
      [class^="ri-"], [class*=" ri-"] {
        font-family: "remixicon" !important;
      }
      ```
      
      ### Icons Look Blurry
      
      Use multiples of 24px for crisp rendering:
      
      ```tsx
      // Good
      <RiHomeLine size={24} />
      <RiHomeLine size={48} />
      
      // Bad (breaks pixel grid)
      <RiHomeLine size={20} />
      <RiHomeLine size={30} />
      ```
      
      ### Wrong Icon Size
      
      **Set parent font-size:**
      ```css
      .icon-container {
        font-size: 24px;
      }
      ```
      
      **Or use size prop:**
      ```tsx
      <RiHomeLine size={24} />
      ```
      
      ## Best Practices
      
      1. **Choose style consistently** - Use line or fill throughout app
      2. **Maintain 24px grid** - Use sizes: 24, 48, 72, 96px
      3. **Provide accessibility** - Add aria-labels to icon-only buttons
      4. **Use currentColor** - Icons inherit text color by default
      5. **Optimize bundle** - Use React package for tree-shaking
      6. **Cache webfonts** - CDN or long cache headers
      7. **Lazy load icons** - Dynamic import for heavy icon sets
      8. **Test on devices** - Ensure icons scale properly
      9. **Document usage** - Create icon component library
      10. **Version lock** - Pin RemixIcon version for consistency
      
      ## Resources
      
      - Website: https://remixicon.com
      - GitHub: https://github.com/Remix-Design/RemixIcon
      - React Package: @remixicon/react
      - Vue Package: @remixicon/vue
      - License: Apache 2.0
      - Total Icons: 3,100+
      - Current Version: 4.7.0
      
    • turborepo-caching.md 9.5 KB
      # Turborepo Caching Strategies
      
      Local caching, remote caching, cache invalidation, and optimization techniques.
      
      ## Local Caching
      
      ### How It Works
      
      Turborepo caches task outputs based on inputs:
      
      1. **Hash inputs**: Source files, dependencies, environment variables, config
      2. **Run task**: If hash not in cache
      3. **Save outputs**: Store in `.turbo/cache`
      4. **Restore on match**: Instant completion on cache hit
      
      Default cache location: `./node_modules/.cache/turbo`
      
      ### Cache Configuration
      
      ```json
      // turbo.json
      {
        "pipeline": {
          "build": {
            "outputs": ["dist/**", ".next/**", "!.next/cache/**"],
            "cache": true  // default
          },
          "dev": {
            "cache": false  // don't cache dev servers
          }
        }
      }
      ```
      
      ### Outputs Configuration
      
      Specify what gets cached:
      
      ```json
      {
        "build": {
          "outputs": [
            "dist/**",              // All files in dist
            "build/**",             // Build directory
            ".next/**",             // Next.js output
            "!.next/cache/**",      // Exclude Next.js cache
            "storybook-static/**",  // Storybook build
            "*.tsbuildinfo"         // TypeScript build info
          ]
        }
      }
      ```
      
      **Best practices:**
      - Include all build artifacts
      - Exclude nested caches
      - Include type definitions
      - Include generated files
      
      ### Clear Local Cache
      
      ```bash
      # Remove cache directory
      rm -rf ./node_modules/.cache/turbo
      
      # Or use turbo command with --force
      turbo run build --force
      
      # Clear and rebuild
      turbo run clean && turbo run build
      ```
      
      ## Remote Caching
      
      Share cache across team and CI/CD.
      
      ### Vercel Remote Cache (Recommended)
      
      **Setup:**
      ```bash
      # Login to Vercel
      turbo login
      
      # Link repository
      turbo link
      ```
      
      **Use in CI:**
      ```yaml
      # .github/workflows/ci.yml
      env:
        TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
        TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
      
      steps:
        - run: turbo run build test
      ```
      
      Get tokens from Vercel dashboard:
      1. Go to https://vercel.com/account/tokens
      2. Create new token
      3. Add as GitHub secrets
      
      ### Custom Remote Cache
      
      Configure custom remote cache server:
      
      ```json
      // .turbo/config.json
      {
        "teamid": "team_xxx",
        "apiurl": "https://cache.example.com",
        "token": "your-token"
      }
      ```
      
      Or use environment variables:
      ```bash
      export TURBO_API="https://cache.example.com"
      export TURBO_TOKEN="your-token"
      export TURBO_TEAM="team_xxx"
      ```
      
      ### Remote Cache Verification
      
      ```bash
      # Check cache status
      turbo run build --output-logs=hash-only
      
      # Output shows:
      # • web:build: cache hit, replaying logs [hash]
      # • api:build: cache miss, executing [hash]
      ```
      
      ## Cache Signatures
      
      Cache invalidated when these change:
      
      ### 1. Source Files
      
      All tracked Git files in package:
      ```
      packages/ui/
      ├── src/
      │   ├── button.tsx     # Tracked
      │   └── input.tsx      # Tracked
      ├── dist/              # Ignored (in .gitignore)
      └── node_modules/      # Ignored
      ```
      
      ### 2. Package Dependencies
      
      Changes in package.json:
      ```json
      {
        "dependencies": {
          "react": "18.2.0"  // Version change invalidates cache
        }
      }
      ```
      
      ### 3. Environment Variables
      
      Configured in pipeline:
      ```json
      {
        "build": {
          "env": ["NODE_ENV", "API_URL"]  // Changes invalidate cache
        }
      }
      ```
      
      ### 4. Global Dependencies
      
      Files affecting all packages:
      ```json
      {
        "globalDependencies": [
          "**/.env.*local",
          "tsconfig.json",
          ".eslintrc.js"
        ]
      }
      ```
      
      ### 5. Task Configuration
      
      Changes to turbo.json pipeline:
      ```json
      {
        "build": {
          "dependsOn": ["^build"],
          "outputs": ["dist/**"]  // Config changes invalidate cache
        }
      }
      ```
      
      ## Input Control
      
      ### Override Input Detection
      
      Explicitly define what affects cache:
      
      ```json
      {
        "build": {
          "inputs": [
            "src/**/*.ts",           // Include TS files
            "src/**/*.tsx",          // Include TSX files
            "!src/**/*.test.ts",     // Exclude tests
            "!src/**/*.stories.tsx", // Exclude stories
            "package.json",          // Include package.json
            "tsconfig.json"          // Include config
          ]
        }
      }
      ```
      
      Use cases:
      - Exclude test files from build cache
      - Exclude documentation from production builds
      - Include only source files, not generated files
      
      ### Global vs Package Inputs
      
      **Global inputs** (affect all packages):
      ```json
      {
        "globalDependencies": [".env", "tsconfig.json"]
      }
      ```
      
      **Package inputs** (affect specific tasks):
      ```json
      {
        "pipeline": {
          "build": {
            "inputs": ["src/**"]
          }
        }
      }
      ```
      
      ## Environment Variables
      
      ### Cached Environment Variables
      
      Include in cache signature:
      
      ```json
      {
        "pipeline": {
          "build": {
            "env": [
              "NODE_ENV",           // Must match for cache hit
              "NEXT_PUBLIC_API_URL",
              "DATABASE_URL"
            ]
          }
        }
      }
      ```
      
      Cache invalidated when values change.
      
      ### Pass-Through Environment Variables
      
      Don't affect cache:
      
      ```json
      {
        "pipeline": {
          "build": {
            "passThroughEnv": [
              "DEBUG",        // Different values use same cache
              "LOG_LEVEL",
              "VERBOSE"
            ]
          }
        }
      }
      ```
      
      Use for: Debug flags, log levels, non-production settings
      
      ### Global Environment Variables
      
      Available to all tasks:
      
      ```json
      {
        "globalEnv": [
          "NODE_ENV",
          "CI",
          "VERCEL"
        ]
      }
      ```
      
      ## Cache Optimization Strategies
      
      ### 1. Granular Outputs
      
      Define precise outputs to minimize cache size:
      
      ```json
      // ❌ Bad - caches too much
      {
        "build": {
          "outputs": ["**"]
        }
      }
      
      // ✅ Good - specific outputs
      {
        "build": {
          "outputs": ["dist/**", "!dist/**/*.map"]
        }
      }
      ```
      
      ### 2. Exclude Unnecessary Files
      
      ```json
      {
        "build": {
          "outputs": [
            ".next/**",
            "!.next/cache/**",      // Exclude Next.js cache
            "!.next/server/**/*.js.map",  // Exclude source maps
            "!.next/static/**/*.map"
          ]
        }
      }
      ```
      
      ### 3. Separate Cacheable Tasks
      
      ```json
      {
        "pipeline": {
          "build": {
            "dependsOn": ["^build"],
            "cache": true
          },
          "test": {
            "dependsOn": ["build"],
            "cache": true  // Separate from build
          },
          "dev": {
            "cache": false  // Never cache
          }
        }
      }
      ```
      
      ### 4. Use Input Filters
      
      Only track relevant files:
      
      ```json
      {
        "build": {
          "inputs": [
            "src/**/*.{ts,tsx}",
            "!src/**/*.{test,spec}.{ts,tsx}",
            "public/**",
            "package.json"
          ]
        }
      }
      ```
      
      ## Cache Analysis
      
      ### Inspect Cache Hits/Misses
      
      ```bash
      # Dry run with JSON output
      turbo run build --dry-run=json | jq '.tasks[] | {package: .package, task: .task, cache: .cache}'
      ```
      
      ### View Task Graph
      
      ```bash
      # Generate task graph
      turbo run build --graph
      
      # Output: graph.html (open in browser)
      ```
      
      ### Cache Statistics
      
      ```bash
      # Run with summary
      turbo run build --summarize
      
      # Output: .turbo/runs/[hash].json
      ```
      
      ## CI/CD Cache Configuration
      
      ### GitHub Actions
      
      ```yaml
      name: CI
      on: [push, pull_request]
      
      jobs:
        build:
          runs-on: ubuntu-latest
          steps:
            - uses: actions/checkout@v4
      
            - uses: actions/setup-node@v4
              with:
                node-version: 18
      
            - name: Install dependencies
              run: npm install
      
            - name: Build and test
              run: turbo run build test lint
              env:
                TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
                TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
      
            # Optional: Cache node_modules
            - uses: actions/cache@v3
              with:
                path: node_modules
                key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
      ```
      
      ### GitLab CI
      
      ```yaml
      image: node:18
      
      cache:
        key: ${CI_COMMIT_REF_SLUG}
        paths:
          - node_modules/
          - .turbo/
      
      build:
        stage: build
        script:
          - npm install
          - turbo run build test
        variables:
          TURBO_TOKEN: $TURBO_TOKEN
          TURBO_TEAM: $TURBO_TEAM
      ```
      
      ## Troubleshooting
      
      ### Cache Not Working
      
      **Check outputs are defined:**
      ```bash
      turbo run build --dry-run=json | jq '.tasks[] | {task: .task, outputs: .outputs}'
      ```
      
      **Verify cache location:**
      ```bash
      ls -la ./node_modules/.cache/turbo
      ```
      
      **Check environment variables:**
      ```bash
      echo $TURBO_TOKEN
      echo $TURBO_TEAM
      ```
      
      ### Cache Too Large
      
      **Analyze cache size:**
      ```bash
      du -sh ./node_modules/.cache/turbo
      ```
      
      **Reduce outputs:**
      ```json
      {
        "build": {
          "outputs": [
            "dist/**",
            "!dist/**/*.map",      // Exclude source maps
            "!dist/**/*.test.js"   // Exclude test files
          ]
        }
      }
      ```
      
      **Clear old cache:**
      ```bash
      # Turborepo doesn't auto-clean, manually remove:
      rm -rf ./node_modules/.cache/turbo
      ```
      
      ### Remote Cache Connection Issues
      
      **Test connection:**
      ```bash
      curl -I https://cache.example.com
      ```
      
      **Verify token:**
      ```bash
      turbo link
      # Should show: "Remote caching enabled"
      ```
      
      **Check logs:**
      ```bash
      turbo run build --output-logs=full
      ```
      
      ## Best Practices
      
      1. **Define precise outputs** - Only cache necessary files
      2. **Exclude nested caches** - Don't cache caches (.next/cache)
      3. **Use remote caching** - Share cache across team and CI
      4. **Track relevant inputs** - Use `inputs` to filter files
      5. **Separate env vars** - Use `passThroughEnv` for debug flags
      6. **Cache test results** - Include coverage in outputs
      7. **Don't cache dev servers** - Set `cache: false` for dev tasks
      8. **Use global dependencies** - Share config across packages
      9. **Monitor cache performance** - Use `--summarize` to analyze
      10. **Clear cache periodically** - Remove stale cache manually
      
      ## Cache Performance Tips
      
      **For CI/CD:**
      - Enable remote caching
      - Run only changed packages: `--filter='...[origin/main]'`
      - Use `--continue` to see all errors
      - Cache node_modules separately
      
      **For Local Development:**
      - Keep local cache enabled
      - Don't force rebuild unless needed
      - Use filters to build only what changed
      - Clear cache if issues arise
      
      **For Large Monorepos:**
      - Use granular outputs
      - Implement input filters
      - Monitor cache size regularly
      - Consider cache size limits on remote cache
      
    • turborepo-pipelines.md 8.3 KB
      # Turborepo Task Pipelines
      
      Task orchestration, dependencies, and parallel execution strategies.
      
      ## Pipeline Configuration
      
      Define tasks in `turbo.json`:
      
      ```json
      {
        "$schema": "https://turbo.build/schema.json",
        "pipeline": {
          "build": {
            "dependsOn": ["^build"],
            "outputs": ["dist/**", ".next/**"]
          },
          "test": {
            "dependsOn": ["build"],
            "outputs": ["coverage/**"]
          },
          "lint": {},
          "dev": {
            "cache": false,
            "persistent": true
          }
        }
      }
      ```
      
      ## Task Dependencies
      
      ### Topological Dependencies (^)
      
      `^` means "run this task in dependencies first":
      
      ```json
      {
        "pipeline": {
          "build": {
            "dependsOn": ["^build"]
          }
        }
      }
      ```
      
      Example flow:
      ```
      packages/ui (dependency)
        ↓ builds first
      apps/web (depends on @repo/ui)
        ↓ builds second
      ```
      
      ### Internal Dependencies
      
      Run tasks in same package first:
      
      ```json
      {
        "pipeline": {
          "deploy": {
            "dependsOn": ["build", "test"]
          }
        }
      }
      ```
      
      Execution order in same package:
      1. Run `build`
      2. Run `test`
      3. Run `deploy`
      
      ### Combined Dependencies
      
      Mix topological and internal:
      
      ```json
      {
        "pipeline": {
          "test": {
            "dependsOn": ["^build", "lint"]
          }
        }
      }
      ```
      
      Execution order:
      1. Build all dependencies (`^build`)
      2. Lint current package (`lint`)
      3. Run tests (`test`)
      
      ## Task Configuration Options
      
      ### outputs
      
      Define what gets cached:
      
      ```json
      {
        "build": {
          "outputs": [
            "dist/**",           // All files in dist
            ".next/**",          // Next.js build
            "!.next/cache/**",   // Exclude Next.js cache
            "build/**",          // Build directory
            "public/dist/**"     // Public assets
          ]
        }
      }
      ```
      
      ### cache
      
      Enable/disable caching:
      
      ```json
      {
        "dev": {
          "cache": false        // Don't cache dev server
        },
        "build": {
          "cache": true         // Cache build (default)
        }
      }
      ```
      
      ### persistent
      
      Keep task running (for dev servers):
      
      ```json
      {
        "dev": {
          "cache": false,
          "persistent": true    // Don't kill after completion
        }
      }
      ```
      
      ### env
      
      Environment variables affecting output:
      
      ```json
      {
        "build": {
          "env": [
            "NODE_ENV",
            "NEXT_PUBLIC_API_URL",
            "DATABASE_URL"
          ]
        }
      }
      ```
      
      ### passThroughEnv
      
      Pass env vars without affecting cache:
      
      ```json
      {
        "build": {
          "passThroughEnv": [
            "DEBUG",            // Pass through but don't invalidate cache
            "LOG_LEVEL"
          ]
        }
      }
      ```
      
      ### inputs
      
      Override default input detection:
      
      ```json
      {
        "build": {
          "inputs": [
            "src/**/*.ts",
            "!src/**/*.test.ts", // Exclude test files
            "package.json"
          ]
        }
      }
      ```
      
      ### outputMode
      
      Control output display:
      
      ```json
      {
        "build": {
          "outputMode": "full"        // Show all output
        },
        "dev": {
          "outputMode": "hash-only"   // Show cache hash only
        },
        "test": {
          "outputMode": "new-only"    // Show new output only
        },
        "lint": {
          "outputMode": "errors-only" // Show errors only
        }
      }
      ```
      
      ## Running Tasks
      
      ### Basic Execution
      
      ```bash
      # Run build in all packages
      turbo run build
      
      # Run multiple tasks
      turbo run build test lint
      
      # Run with specific package manager
      pnpm turbo run build
      ```
      
      ### Filtering
      
      Run tasks in specific packages:
      
      ```bash
      # Single package
      turbo run build --filter=web
      turbo run build --filter=@repo/ui
      
      # Multiple packages
      turbo run build --filter=web --filter=api
      
      # All apps
      turbo run build --filter='./apps/*'
      
      # Pattern matching
      turbo run test --filter='*-api'
      ```
      
      ### Dependency Filtering
      
      ```bash
      # Package and its dependencies
      turbo run build --filter='...web'
      
      # Package's dependencies only (exclude package itself)
      turbo run build --filter='...^web'
      
      # Package and its dependents
      turbo run test --filter='ui...'
      
      # Package's dependents only
      turbo run test --filter='^ui...'
      ```
      
      ### Git-Based Filtering
      
      Run only on changed packages:
      
      ```bash
      # Changed since main branch
      turbo run build --filter='[main]'
      
      # Changed since HEAD~1
      turbo run build --filter='[HEAD~1]'
      
      # Changed in working directory
      turbo run test --filter='...[HEAD]'
      
      # Package and dependencies, only if changed
      turbo run build --filter='...[origin/main]'
      ```
      
      ## Concurrency Control
      
      ### Parallel Execution (Default)
      
      Turborepo runs tasks in parallel when safe:
      
      ```bash
      # Run with default parallelism
      turbo run build
      ```
      
      ### Limit Concurrency
      
      ```bash
      # Max 3 tasks at once
      turbo run build --concurrency=3
      
      # 50% of CPU cores
      turbo run build --concurrency=50%
      
      # No parallelism (sequential)
      turbo run build --concurrency=1
      ```
      
      ### Continue on Error
      
      ```bash
      # Don't stop on first error
      turbo run test --continue
      ```
      
      ## Task Execution Order
      
      Example monorepo:
      ```
      apps/
      ├── web (depends on @repo/ui, @repo/utils)
      └── docs (depends on @repo/ui)
      packages/
      ├── ui (depends on @repo/utils)
      └── utils (no dependencies)
      ```
      
      With config:
      ```json
      {
        "pipeline": {
          "build": {
            "dependsOn": ["^build"]
          }
        }
      }
      ```
      
      Execution order for `turbo run build`:
      1. **Wave 1** (parallel): `@repo/utils` (no dependencies)
      2. **Wave 2** (parallel): `@repo/ui` (depends on utils)
      3. **Wave 3** (parallel): `web` and `docs` (both depend on ui)
      
      ## Complex Pipeline Examples
      
      ### Full-Stack Application
      
      ```json
      {
        "pipeline": {
          "build": {
            "dependsOn": ["^build"],
            "outputs": [".next/**", "dist/**"]
          },
          "test": {
            "dependsOn": ["^build"],
            "outputs": ["coverage/**"]
          },
          "lint": {
            "dependsOn": ["^build"]
          },
          "typecheck": {
            "dependsOn": ["^build"]
          },
          "dev": {
            "cache": false,
            "persistent": true
          },
          "deploy": {
            "dependsOn": ["build", "test", "lint", "typecheck"]
          }
        }
      }
      ```
      
      ### Monorepo with Code Generation
      
      ```json
      {
        "pipeline": {
          "generate": {
            "cache": false,
            "outputs": ["src/generated/**"]
          },
          "build": {
            "dependsOn": ["^build", "generate"],
            "outputs": ["dist/**"]
          },
          "test": {
            "dependsOn": ["generate"],
            "outputs": ["coverage/**"]
          }
        }
      }
      ```
      
      ### Database-Dependent Pipeline
      
      ```json
      {
        "pipeline": {
          "db:generate": {
            "cache": false
          },
          "db:migrate": {
            "cache": false
          },
          "build": {
            "dependsOn": ["^build", "db:generate"],
            "outputs": ["dist/**"]
          },
          "test:unit": {
            "dependsOn": ["build"]
          },
          "test:integration": {
            "dependsOn": ["db:migrate"],
            "cache": false
          }
        }
      }
      ```
      
      ## Dry Run
      
      Preview execution without running:
      
      ```bash
      # See what would run
      turbo run build --dry-run
      
      # JSON output for scripts
      turbo run build --dry-run=json
      
      # Show full task graph
      turbo run build --graph
      ```
      
      ## Force Execution
      
      Ignore cache and run tasks:
      
      ```bash
      # Force rebuild everything
      turbo run build --force
      
      # Force specific package
      turbo run build --filter=web --force
      ```
      
      ## Output Control
      
      ```bash
      # Show only errors
      turbo run build --output-logs=errors-only
      
      # Show new logs only
      turbo run build --output-logs=new-only
      
      # Show cache hash only
      turbo run build --output-logs=hash-only
      
      # Show full output
      turbo run build --output-logs=full
      ```
      
      ## Best Practices
      
      1. **Use topological dependencies** - `^build` ensures correct build order
      2. **Cache build outputs** - Define `outputs` for faster rebuilds
      3. **Disable cache for dev** - Set `cache: false` for dev servers
      4. **Mark persistent tasks** - Use `persistent: true` for long-running tasks
      5. **Filter strategically** - Use filters to run only affected tasks
      6. **Control concurrency** - Limit parallelism for resource-intensive tasks
      7. **Configure env vars** - Include vars that affect output in `env`
      8. **Use dry-run** - Preview execution plan before running
      9. **Continue on error in CI** - Use `--continue` to see all errors
      10. **Leverage git filtering** - Run only on changed packages in CI
      
      ## Common Patterns
      
      ### CI/CD Pipeline
      
      ```yaml
      # .github/workflows/ci.yml
      jobs:
        build:
          steps:
            - run: turbo run build test lint --filter='...[origin/main]'
      ```
      
      Only build/test/lint changed packages and their dependents.
      
      ### Development Workflow
      
      ```bash
      # Start all dev servers
      turbo run dev
      
      # Start specific app with dependencies
      turbo run dev --filter=web...
      ```
      
      ### Pre-commit Hook
      
      ```json
      // package.json
      {
        "scripts": {
          "pre-commit": "turbo run lint test --filter='...[HEAD]'"
        }
      }
      ```
      
      Only lint/test changed packages.
      
      ### Deployment
      
      ```bash
      # Build and test specific app
      turbo run build test --filter=web...
      
      # Deploy if successful
      turbo run deploy --filter=web
      ```
      
      Build app and its dependencies, then deploy.
      
    • turborepo-setup.md 9.5 KB
      # Turborepo Setup & Configuration
      
      Installation, workspace configuration, and project structure for monorepos.
      
      ## Installation
      
      ### Create New Monorepo
      
      Using official starter:
      ```bash
      npx create-turbo@latest my-monorepo
      cd my-monorepo
      ```
      
      Interactive prompts:
      - Project name
      - Package manager (npm, yarn, pnpm, bun)
      - Example template
      
      ### Manual Installation
      
      Install in existing project:
      ```bash
      # npm
      npm install turbo --save-dev
      
      # yarn
      yarn add turbo --dev
      
      # pnpm
      pnpm add turbo --save-dev
      
      # bun
      bun add turbo --dev
      ```
      
      ## Workspace Configuration
      
      ### Package Manager Setup
      
      **pnpm (recommended):**
      ```yaml
      # pnpm-workspace.yaml
      packages:
        - 'apps/*'
        - 'packages/*'
      ```
      
      **npm/yarn:**
      ```json
      // package.json (root)
      {
        "name": "my-monorepo",
        "private": true,
        "workspaces": [
          "apps/*",
          "packages/*"
        ]
      }
      ```
      
      ### Root Package.json
      
      ```json
      {
        "name": "my-monorepo",
        "private": true,
        "workspaces": ["apps/*", "packages/*"],
        "scripts": {
          "build": "turbo run build",
          "dev": "turbo run dev",
          "lint": "turbo run lint",
          "test": "turbo run test",
          "clean": "turbo run clean"
        },
        "devDependencies": {
          "turbo": "latest",
          "typescript": "^5.0.0"
        },
        "packageManager": "pnpm@8.0.0"
      }
      ```
      
      ## Project Structure
      
      ### Recommended Directory Structure
      
      ```
      my-monorepo/
      ├── apps/                    # Applications
      │   ├── web/                # Next.js web app
      │   │   ├── app/
      │   │   ├── package.json
      │   │   └── next.config.js
      │   ├── docs/               # Documentation site
      │   │   ├── app/
      │   │   └── package.json
      │   └── api/                # Backend API
      │       ├── src/
      │       └── package.json
      ├── packages/               # Shared packages
      │   ├── ui/                 # UI component library
      │   │   ├── src/
      │   │   ├── package.json
      │   │   └── tsconfig.json
      │   ├── config/             # Shared configs
      │   │   ├── eslint/
      │   │   └── typescript/
      │   ├── utils/              # Utility functions
      │   │   ├── src/
      │   │   └── package.json
      │   └── types/              # Shared TypeScript types
      │       ├── src/
      │       └── package.json
      ├── turbo.json              # Turborepo config
      ├── package.json            # Root package.json
      ├── pnpm-workspace.yaml     # Workspace config (pnpm)
      └── .gitignore
      ```
      
      ## Application Package Setup
      
      ### Next.js App
      
      ```json
      // apps/web/package.json
      {
        "name": "web",
        "version": "1.0.0",
        "private": true,
        "scripts": {
          "dev": "next dev",
          "build": "next build",
          "start": "next start",
          "lint": "next lint"
        },
        "dependencies": {
          "@repo/ui": "*",
          "@repo/utils": "*",
          "next": "latest",
          "react": "latest",
          "react-dom": "latest"
        },
        "devDependencies": {
          "@repo/typescript-config": "*",
          "@repo/eslint-config": "*",
          "typescript": "^5.0.0"
        }
      }
      ```
      
      ### Backend API App
      
      ```json
      // apps/api/package.json
      {
        "name": "api",
        "version": "1.0.0",
        "private": true,
        "scripts": {
          "dev": "tsx watch src/index.ts",
          "build": "tsup src/index.ts",
          "start": "node dist/index.js",
          "lint": "eslint src/"
        },
        "dependencies": {
          "@repo/utils": "*",
          "@repo/types": "*",
          "express": "^4.18.0"
        },
        "devDependencies": {
          "@repo/typescript-config": "*",
          "@types/express": "^4.17.0",
          "tsx": "^4.0.0",
          "tsup": "^8.0.0"
        }
      }
      ```
      
      ## Shared Package Setup
      
      ### UI Component Library
      
      ```json
      // packages/ui/package.json
      {
        "name": "@repo/ui",
        "version": "0.0.0",
        "main": "./dist/index.js",
        "types": "./dist/index.d.ts",
        "exports": {
          ".": {
            "types": "./dist/index.d.ts",
            "default": "./dist/index.js"
          },
          "./button": {
            "types": "./dist/button.d.ts",
            "default": "./dist/button.js"
          }
        },
        "scripts": {
          "build": "tsc",
          "dev": "tsc --watch",
          "lint": "eslint src/",
          "clean": "rm -rf dist"
        },
        "dependencies": {
          "react": "latest"
        },
        "devDependencies": {
          "@repo/typescript-config": "*",
          "typescript": "^5.0.0"
        }
      }
      ```
      
      ```json
      // packages/ui/tsconfig.json
      {
        "extends": "@repo/typescript-config/react-library.json",
        "compilerOptions": {
          "outDir": "dist",
          "declarationDir": "dist"
        },
        "include": ["src"],
        "exclude": ["node_modules", "dist"]
      }
      ```
      
      ### Utility Library
      
      ```json
      // packages/utils/package.json
      {
        "name": "@repo/utils",
        "version": "0.0.0",
        "main": "./dist/index.js",
        "types": "./dist/index.d.ts",
        "scripts": {
          "build": "tsc",
          "dev": "tsc --watch",
          "test": "jest"
        },
        "devDependencies": {
          "@repo/typescript-config": "*",
          "jest": "^29.0.0",
          "typescript": "^5.0.0"
        }
      }
      ```
      
      ## Shared Configuration Packages
      
      ### TypeScript Config Package
      
      ```
      packages/typescript-config/
      ├── base.json
      ├── nextjs.json
      ├── react-library.json
      └── package.json
      ```
      
      ```json
      // packages/typescript-config/package.json
      {
        "name": "@repo/typescript-config",
        "version": "0.0.0",
        "main": "base.json",
        "files": [
          "base.json",
          "nextjs.json",
          "react-library.json"
        ]
      }
      ```
      
      ```json
      // packages/typescript-config/base.json
      {
        "compilerOptions": {
          "strict": true,
          "esModuleInterop": true,
          "skipLibCheck": true,
          "forceConsistentCasingInFileNames": true,
          "resolveJsonModule": true,
          "isolatedModules": true,
          "moduleResolution": "bundler",
          "target": "ES2020",
          "module": "ESNext"
        },
        "exclude": ["node_modules"]
      }
      ```
      
      ```json
      // packages/typescript-config/nextjs.json
      {
        "extends": "./base.json",
        "compilerOptions": {
          "jsx": "preserve",
          "lib": ["dom", "dom.iterable", "esnext"],
          "allowJs": true,
          "noEmit": true,
          "incremental": true,
          "plugins": [{ "name": "next" }]
        },
        "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
        "exclude": ["node_modules"]
      }
      ```
      
      ### ESLint Config Package
      
      ```
      packages/eslint-config/
      ├── library.js
      ├── next.js
      └── package.json
      ```
      
      ```json
      // packages/eslint-config/package.json
      {
        "name": "@repo/eslint-config",
        "version": "0.0.0",
        "main": "library.js",
        "files": ["library.js", "next.js"],
        "dependencies": {
          "eslint-config-next": "latest",
          "eslint-config-prettier": "^9.0.0",
          "eslint-plugin-react": "latest"
        }
      }
      ```
      
      ```js
      // packages/eslint-config/library.js
      module.exports = {
        extends: ['eslint:recommended', 'prettier'],
        env: {
          node: true,
          es2020: true,
        },
        parserOptions: {
          ecmaVersion: 2020,
          sourceType: 'module',
        },
        rules: {
          'no-console': 'warn',
        },
      }
      ```
      
      ```js
      // packages/eslint-config/next.js
      module.exports = {
        extends: ['next', 'prettier'],
        rules: {
          '@next/next/no-html-link-for-pages': 'off',
        },
      }
      ```
      
      ## Dependency Management
      
      ### Internal Dependencies
      
      Use workspace protocol:
      
      **pnpm:**
      ```json
      {
        "dependencies": {
          "@repo/ui": "workspace:*"
        }
      }
      ```
      
      **npm/yarn:**
      ```json
      {
        "dependencies": {
          "@repo/ui": "*"
        }
      }
      ```
      
      ### Version Syncing
      
      Keep dependencies in sync across packages:
      
      ```json
      // Root package.json
      {
        "devDependencies": {
          "react": "18.2.0",
          "react-dom": "18.2.0",
          "typescript": "5.0.0"
        }
      }
      ```
      
      Packages inherit from root or specify versions explicitly.
      
      ## Turbo.json Configuration
      
      Basic configuration file:
      
      ```json
      {
        "$schema": "https://turbo.build/schema.json",
        "globalDependencies": [
          "**/.env.*local",
          "tsconfig.json"
        ],
        "globalEnv": [
          "NODE_ENV"
        ],
        "pipeline": {
          "build": {
            "dependsOn": ["^build"],
            "outputs": [".next/**", "!.next/cache/**", "dist/**"]
          },
          "dev": {
            "cache": false,
            "persistent": true
          },
          "lint": {
            "dependsOn": ["^build"]
          },
          "test": {
            "dependsOn": ["build"],
            "outputs": ["coverage/**"]
          },
          "clean": {
            "cache": false
          }
        }
      }
      ```
      
      ## Environment Variables
      
      ### Global Environment Variables
      
      ```json
      // turbo.json
      {
        "globalEnv": [
          "NODE_ENV",
          "CI"
        ]
      }
      ```
      
      ### Package-Specific Environment Variables
      
      ```json
      {
        "pipeline": {
          "build": {
            "env": ["NEXT_PUBLIC_API_URL", "DATABASE_URL"],
            "passThroughEnv": ["CUSTOM_VAR"]
          }
        }
      }
      ```
      
      ### .env Files
      
      ```
      my-monorepo/
      ├── .env                    # Global env vars
      ├── .env.local             # Local overrides (gitignored)
      ├── apps/
      │   └── web/
      │       ├── .env           # App-specific
      │       └── .env.local     # Local overrides
      ```
      
      ## Gitignore Configuration
      
      ```gitignore
      # Dependencies
      node_modules/
      .pnp
      .pnp.js
      
      # Turbo
      .turbo
      
      # Build outputs
      dist/
      .next/
      out/
      build/
      
      # Environment
      .env.local
      .env.*.local
      
      # Testing
      coverage/
      
      # Misc
      .DS_Store
      *.log
      ```
      
      ## NPM Scripts
      
      Common scripts in root package.json:
      
      ```json
      {
        "scripts": {
          "build": "turbo run build",
          "dev": "turbo run dev",
          "lint": "turbo run lint",
          "test": "turbo run test",
          "format": "prettier --write \"**/*.{ts,tsx,md}\"",
          "clean": "turbo run clean && rm -rf node_modules",
          "typecheck": "turbo run typecheck"
        }
      }
      ```
      
      ## Initialization Checklist
      
      Setting up new Turborepo:
      
      - [ ] Install Turborepo (create-turbo or manual)
      - [ ] Configure workspace (pnpm-workspace.yaml or package.json)
      - [ ] Create directory structure (apps/, packages/)
      - [ ] Set up shared config packages (typescript-config, eslint-config)
      - [ ] Create turbo.json with pipeline
      - [ ] Configure gitignore
      - [ ] Set up environment variables
      - [ ] Define package dependencies
      - [ ] Add root scripts
      - [ ] Test build and dev commands
      
  • scripts
    • tests
      • coverage-web.json 34 KB
        {"meta": {"format": 3, "version": "7.11.0", "timestamp": "2025-11-05T00:56:58.689936", "branch_coverage": false, "show_contexts": false}, "files": {"__init__.py": {"executed_lines": [0], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": [], "functions": {"": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}, "classes": {"": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}}, "nextjs_init.py": {"executed_lines": [2, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20, 44, 45, 46, 47, 48, 49, 50, 51, 53, 55, 56, 58, 59, 63, 64, 66, 68, 69, 71, 73, 76, 79, 81, 82, 83, 84, 85, 87, 88, 89, 92, 93, 94, 96, 98, 101, 102, 105, 106, 109, 110, 112, 113, 115, 117, 120, 121, 124, 125, 127, 129, 132, 133, 138, 139, 142, 143, 144, 149, 150, 151, 156, 157, 158, 159, 161, 162, 165, 166, 169, 170, 172, 174, 180, 182, 183, 192, 193, 194, 196, 197, 198, 199, 201, 215, 217, 221, 222, 240, 255, 257, 267, 269, 282, 284, 306, 308, 310, 312, 313, 328, 342, 344, 352, 354, 359, 361, 379, 381, 416, 418, 460, 462, 467, 469, 479, 481, 482, 483, 484, 485, 486, 487, 489, 490, 491, 492, 494, 495, 496, 497, 498, 499, 500, 501, 508, 546], "summary": {"covered_lines": 146, "num_statements": 162, "percent_covered": 90.12345679012346, "percent_covered_display": "90", "missing_lines": 16, "excluded_lines": 0}, "missing_lines": [503, 504, 505, 510, 513, 514, 517, 520, 523, 524, 525, 526, 530, 532, 543, 547], "excluded_lines": [], "functions": {"NextJSInitializer.__init__": {"executed_lines": [44, 45, 46, 47, 48, 49, 50, 51], "summary": {"covered_lines": 8, "num_statements": 8, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer.validate_name": {"executed_lines": [55, 56, 58, 59, 63, 64], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer.check_directory": {"executed_lines": [68, 69], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer.create_directory_structure": {"executed_lines": [73, 76, 79, 81, 82, 83, 84, 85, 87, 88, 89, 92, 93, 94], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._create_app_router_files": {"executed_lines": [98, 101, 102, 105, 106, 109, 110, 112, 113], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._create_pages_router_files": {"executed_lines": [117, 120, 121, 124, 125], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer.create_config_files": {"executed_lines": [129, 132, 133, 138, 139, 142, 143, 144, 149, 150, 151, 156, 157, 158, 159, 161, 162, 165, 166, 169, 170], "summary": {"covered_lines": 21, "num_statements": 21, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_package_json": {"executed_lines": [174, 180, 182, 183, 192, 193, 194, 196, 197, 198, 199, 201], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_layout_content": {"executed_lines": [217, 221, 222, 240], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_page_content": {"executed_lines": [257], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_next_config": {"executed_lines": [269], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_tsconfig": {"executed_lines": [284], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_eslint_config": {"executed_lines": [308], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_tailwind_config": {"executed_lines": [312, 313, 328], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_postcss_config": {"executed_lines": [344], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_tailwind_globals": {"executed_lines": [354], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_basic_globals": {"executed_lines": [361], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_gitignore": {"executed_lines": [381], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_readme": {"executed_lines": [418], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_app_content": {"executed_lines": [462], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer._get_index_content": {"executed_lines": [469], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "NextJSInitializer.initialize": {"executed_lines": [481, 482, 483, 484, 485, 486, 487, 489, 490, 491, 492, 494, 495, 496, 497, 498, 499, 500, 501], "summary": {"covered_lines": 19, "num_statements": 22, "percent_covered": 86.36363636363636, "percent_covered_display": "86", "missing_lines": 3, "excluded_lines": 0}, "missing_lines": [503, 504, 505], "excluded_lines": []}, "main": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0}, "missing_lines": [510, 513, 514, 517, 520, 523, 524, 525, 526, 530, 532, 543], "excluded_lines": []}, "": {"executed_lines": [2, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20, 53, 66, 71, 96, 115, 127, 172, 215, 255, 267, 282, 306, 310, 342, 352, 359, 379, 416, 460, 467, 479, 508, 546], "summary": {"covered_lines": 32, "num_statements": 33, "percent_covered": 96.96969696969697, "percent_covered_display": "97", "missing_lines": 1, "excluded_lines": 0}, "missing_lines": [547], "excluded_lines": []}}, "classes": {"NextJSInitializer": {"executed_lines": [44, 45, 46, 47, 48, 49, 50, 51, 55, 56, 58, 59, 63, 64, 68, 69, 73, 76, 79, 81, 82, 83, 84, 85, 87, 88, 89, 92, 93, 94, 98, 101, 102, 105, 106, 109, 110, 112, 113, 117, 120, 121, 124, 125, 129, 132, 133, 138, 139, 142, 143, 144, 149, 150, 151, 156, 157, 158, 159, 161, 162, 165, 166, 169, 170, 174, 180, 182, 183, 192, 193, 194, 196, 197, 198, 199, 201, 217, 221, 222, 240, 257, 269, 284, 308, 312, 313, 328, 344, 354, 361, 381, 418, 462, 469, 481, 482, 483, 484, 485, 486, 487, 489, 490, 491, 492, 494, 495, 496, 497, 498, 499, 500, 501], "summary": {"covered_lines": 114, "num_statements": 117, "percent_covered": 97.43589743589743, "percent_covered_display": "97", "missing_lines": 3, "excluded_lines": 0}, "missing_lines": [503, 504, 505], "excluded_lines": []}, "": {"executed_lines": [2, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20, 53, 66, 71, 96, 115, 127, 172, 215, 255, 267, 282, 306, 310, 342, 352, 359, 379, 416, 460, 467, 479, 508, 546], "summary": {"covered_lines": 32, "num_statements": 45, "percent_covered": 71.11111111111111, "percent_covered_display": "71", "missing_lines": 13, "excluded_lines": 0}, "missing_lines": [510, 513, 514, 517, 520, 523, 524, 525, 526, 530, 532, 543, 547], "excluded_lines": []}}}, "tests/test_nextjs_init.py": {"executed_lines": [1, 3, 4, 5, 7, 10, 12, 15, 16, 18, 20, 25, 26, 27, 28, 29, 30, 32, 34, 36, 37, 41, 43, 45, 52, 53, 58, 59, 61, 63, 64, 66, 71, 72, 74, 76, 77, 83, 86, 87, 88, 89, 92, 93, 94, 96, 98, 99, 105, 108, 109, 110, 112, 114, 122, 124, 125, 126, 129, 130, 131, 132, 135, 136, 137, 140, 141, 142, 145, 148, 150, 152, 159, 161, 162, 163, 164, 165, 167, 169, 175, 177, 178, 179, 180, 182, 184, 190, 192, 193, 194, 196, 198, 205, 207, 208, 209, 211, 213, 220, 222, 223, 225, 227, 232, 234, 235, 236, 237, 239, 241, 246, 248, 249, 250, 252, 254, 255, 263, 264, 267, 268, 269, 270, 271, 272, 273, 274, 277, 278, 279, 281, 283, 284, 292, 295, 298, 299, 302, 303, 304, 306, 308, 309, 315, 318, 319], "summary": {"covered_lines": 145, "num_statements": 145, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": [], "functions": {"TestNextJSInitializer.test_init_with_defaults": {"executed_lines": [20, 25, 26, 27, 28, 29, 30], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_validate_name_valid": {"executed_lines": [34, 36, 37, 41], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_validate_name_invalid": {"executed_lines": [45, 52, 53, 58, 59], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_check_directory_exists": {"executed_lines": [63, 64, 66, 71, 72], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_create_directory_structure_app_router": {"executed_lines": [76, 77, 83, 86, 87, 88, 89, 92, 93, 94], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_create_directory_structure_with_src": {"executed_lines": [98, 99, 105, 108, 109, 110], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_package_json_generation": {"executed_lines": [114, 122, 124, 125, 126, 129, 130, 131, 132, 135, 136, 137, 140, 141, 142, 145, 148], "summary": {"covered_lines": 17, "num_statements": 17, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_tsconfig_generation": {"executed_lines": [152, 159, 161, 162, 163, 164, 165], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_layout_content_typescript": {"executed_lines": [169, 175, 177, 178, 179, 180], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_layout_content_javascript": {"executed_lines": [184, 190, 192, 193, 194], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_tailwind_config_typescript": {"executed_lines": [198, 205, 207, 208, 209], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_tailwind_config_javascript": {"executed_lines": [213, 220, 222, 223], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_gitignore_generation": {"executed_lines": [227, 232, 234, 235, 236, 237], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_readme_generation": {"executed_lines": [241, 246, 248, 249, 250], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_create_config_files": {"executed_lines": [254, 255, 263, 264, 267, 268, 269, 270, 271, 272, 273, 274, 277, 278, 279], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_full_initialization": {"executed_lines": [283, 284, 292, 295, 298, 299, 302, 303, 304], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestNextJSInitializer.test_pages_router_structure": {"executed_lines": [308, 309, 315, 318, 319], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [1, 3, 4, 5, 7, 10, 12, 15, 16, 18, 32, 43, 61, 74, 96, 112, 150, 167, 182, 196, 211, 225, 239, 252, 281, 306], "summary": {"covered_lines": 24, "num_statements": 24, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}, "classes": {"TestNextJSInitializer": {"executed_lines": [20, 25, 26, 27, 28, 29, 30, 34, 36, 37, 41, 45, 52, 53, 58, 59, 63, 64, 66, 71, 72, 76, 77, 83, 86, 87, 88, 89, 92, 93, 94, 98, 99, 105, 108, 109, 110, 114, 122, 124, 125, 126, 129, 130, 131, 132, 135, 136, 137, 140, 141, 142, 145, 148, 152, 159, 161, 162, 163, 164, 165, 169, 175, 177, 178, 179, 180, 184, 190, 192, 193, 194, 198, 205, 207, 208, 209, 213, 220, 222, 223, 227, 232, 234, 235, 236, 237, 241, 246, 248, 249, 250, 254, 255, 263, 264, 267, 268, 269, 270, 271, 272, 273, 274, 277, 278, 279, 283, 284, 292, 295, 298, 299, 302, 303, 304, 308, 309, 315, 318, 319], "summary": {"covered_lines": 121, "num_statements": 121, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [1, 3, 4, 5, 7, 10, 12, 15, 16, 18, 32, 43, 61, 74, 96, 112, 150, 167, 182, 196, 211, 225, 239, 252, 281, 306], "summary": {"covered_lines": 24, "num_statements": 24, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}}, "tests/test_turborepo_migrate.py": {"executed_lines": [1, 3, 4, 5, 7, 10, 12, 15, 16, 19, 29, 32, 33, 35, 36, 37, 53, 56, 57, 59, 60, 61, 75, 77, 80, 81, 83, 85, 91, 92, 93, 95, 97, 98, 100, 102, 104, 105, 107, 109, 110, 112, 114, 115, 117, 119, 120, 122, 124, 125, 127, 129, 130, 132, 133, 134, 136, 139, 145, 150, 151, 153, 154, 156, 158, 159, 160, 162, 164, 165, 166, 168, 170, 171, 172, 174, 177, 178, 179, 182, 183, 185, 187, 188, 189, 191, 194, 195, 196, 198, 200, 201, 202, 204, 205, 207, 208, 211, 212, 213, 216, 217, 220, 226, 228, 229, 230, 232, 235, 236, 239, 240, 241, 244, 246, 247, 248, 250, 251, 252, 254, 256, 257, 258, 259, 260, 263, 264, 266, 268, 269, 270, 272, 273, 274, 276, 279, 282, 283, 285, 287, 288, 289, 291, 292, 293, 295, 298, 301, 302, 303, 304, 307, 308, 309, 311, 313, 314, 317, 320, 321, 322, 324, 326, 327, 330, 332, 333, 334, 335, 336, 339, 340, 341, 342, 344, 346, 351, 352, 354, 355, 357, 358, 359, 360, 362, 365, 370, 373, 374], "summary": {"covered_lines": 188, "num_statements": 188, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": [], "functions": {"mock_monorepo": {"executed_lines": [19, 29, 32, 33, 35, 36, 37, 53, 56, 57, 59, 60, 61, 75, 77], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_init": {"executed_lines": [85, 91, 92, 93], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_validate_path_exists": {"executed_lines": [97, 98], "summary": {"covered_lines": 2, "num_statements": 2, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_validate_path_not_exists": {"executed_lines": [102, 104, 105], "summary": {"covered_lines": 3, "num_statements": 3, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_validate_path_not_directory": {"executed_lines": [109, 110, 112, 114, 115], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_validate_path_no_package_json": {"executed_lines": [119, 120, 122, 124, 125], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_analyze_workspace_npm": {"executed_lines": [129, 130, 132, 133, 134], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_analyze_workspace_pnpm": {"executed_lines": [139, 145, 150, 151, 153, 154], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_discover_packages": {"executed_lines": [158, 159, 160, 162, 164, 165, 166], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_analyze_scripts": {"executed_lines": [170, 171, 172, 174, 177, 178, 179, 182, 183], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_infer_build_outputs": {"executed_lines": [187, 188, 189, 191, 194, 195, 196], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_generate_turbo_config": {"executed_lines": [200, 201, 202, 204, 205, 207, 208, 211, 212, 213, 216, 217, 220], "summary": {"covered_lines": 13, "num_statements": 13, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_update_root_package_json": {"executed_lines": [228, 229, 230, 232, 235, 236, 239, 240, 241], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_generate_migration_report": {"executed_lines": [246, 247, 248, 250, 251, 252, 254, 256, 257, 258, 259, 260, 263, 264], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_write_files_dry_run": {"executed_lines": [268, 269, 270, 272, 273, 274, 276, 279, 282, 283], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_write_files_actual": {"executed_lines": [287, 288, 289, 291, 292, 293, 295, 298, 301, 302, 303, 304, 307, 308, 309], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_full_migration_dry_run": {"executed_lines": [313, 314, 317, 320, 321, 322], "summary": {"covered_lines": 6, "num_statements": 6, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_full_migration_actual": {"executed_lines": [326, 327, 330, 332, 333, 334, 335, 336, 339, 340, 341, 342], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_parse_pnpm_workspace": {"executed_lines": [346, 351, 352, 354, 355, 357, 358, 359, 360], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TestTurborepoMigrator.test_monorepo_without_workspaces": {"executed_lines": [365, 370, 373, 374], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [1, 3, 4, 5, 7, 10, 12, 15, 16, 80, 81, 83, 95, 100, 107, 117, 127, 136, 156, 168, 185, 198, 226, 244, 266, 285, 311, 324, 344, 362], "summary": {"covered_lines": 28, "num_statements": 28, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}, "classes": {"TestTurborepoMigrator": {"executed_lines": [85, 91, 92, 93, 97, 98, 102, 104, 105, 109, 110, 112, 114, 115, 119, 120, 122, 124, 125, 129, 130, 132, 133, 134, 139, 145, 150, 151, 153, 154, 158, 159, 160, 162, 164, 165, 166, 170, 171, 172, 174, 177, 178, 179, 182, 183, 187, 188, 189, 191, 194, 195, 196, 200, 201, 202, 204, 205, 207, 208, 211, 212, 213, 216, 217, 220, 228, 229, 230, 232, 235, 236, 239, 240, 241, 246, 247, 248, 250, 251, 252, 254, 256, 257, 258, 259, 260, 263, 264, 268, 269, 270, 272, 273, 274, 276, 279, 282, 283, 287, 288, 289, 291, 292, 293, 295, 298, 301, 302, 303, 304, 307, 308, 309, 313, 314, 317, 320, 321, 322, 326, 327, 330, 332, 333, 334, 335, 336, 339, 340, 341, 342, 346, 351, 352, 354, 355, 357, 358, 359, 360, 365, 370, 373, 374], "summary": {"covered_lines": 145, "num_statements": 145, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [1, 3, 4, 5, 7, 10, 12, 15, 16, 19, 29, 32, 33, 35, 36, 37, 53, 56, 57, 59, 60, 61, 75, 77, 80, 81, 83, 95, 100, 107, 117, 127, 136, 156, 168, 185, 198, 226, 244, 266, 285, 311, 324, 344, 362], "summary": {"covered_lines": 43, "num_statements": 43, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}}, "turborepo_migrate.py": {"executed_lines": [2, 8, 9, 10, 11, 12, 13, 16, 17, 19, 33, 34, 35, 36, 37, 39, 41, 42, 44, 45, 47, 48, 49, 53, 55, 57, 58, 59, 62, 63, 67, 68, 73, 77, 79, 81, 83, 84, 85, 93, 94, 96, 97, 98, 100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 115, 117, 120, 122, 123, 124, 126, 127, 128, 130, 140, 142, 144, 146, 147, 148, 149, 150, 152, 158, 159, 160, 162, 164, 166, 168, 171, 172, 178, 179, 185, 186, 189, 194, 199, 202, 208, 210, 212, 214, 215, 218, 219, 220, 222, 223, 224, 225, 228, 230, 232, 234, 235, 236, 239, 240, 242, 245, 248, 249, 251, 252, 254, 256, 260, 262, 263, 264, 265, 267, 268, 269, 270, 272, 273, 274, 275, 276, 278, 279, 280, 281, 282, 283, 285, 286, 287, 288, 290, 291, 292, 293, 294, 295, 296, 298, 299, 301, 302, 303, 305, 306, 308, 310, 312, 313, 314, 315, 316, 318, 321, 322, 323, 324, 327, 328, 329, 330, 332, 334, 335, 336, 337, 339, 340, 341, 343, 344, 345, 347, 348, 350, 351, 352, 354, 355, 356, 359, 393], "summary": {"covered_lines": 194, "num_statements": 213, "percent_covered": 91.07981220657277, "percent_covered_display": "91", "missing_lines": 19, "excluded_lines": 0}, "missing_lines": [86, 89, 90, 190, 191, 195, 196, 200, 221, 226, 246, 361, 364, 370, 375, 382, 384, 390, 394], "excluded_lines": [], "functions": {"TurborepoMigrator.__init__": {"executed_lines": [33, 34, 35, 36, 37], "summary": {"covered_lines": 5, "num_statements": 5, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TurborepoMigrator.validate_path": {"executed_lines": [41, 42, 44, 45, 47, 48, 49], "summary": {"covered_lines": 7, "num_statements": 7, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TurborepoMigrator.analyze_workspace": {"executed_lines": [55, 57, 58, 59, 62, 63, 67, 68, 73, 77], "summary": {"covered_lines": 10, "num_statements": 10, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TurborepoMigrator.discover_packages": {"executed_lines": [81, 83, 84, 85, 93, 94, 96, 97, 98], "summary": {"covered_lines": 9, "num_statements": 12, "percent_covered": 75.0, "percent_covered_display": "75", "missing_lines": 3, "excluded_lines": 0}, "missing_lines": [86, 89, 90], "excluded_lines": []}, "TurborepoMigrator._parse_pnpm_workspace": {"executed_lines": [102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TurborepoMigrator._find_packages_by_pattern": {"executed_lines": [117, 120, 122, 123, 124, 126, 127, 128, 130], "summary": {"covered_lines": 9, "num_statements": 9, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TurborepoMigrator.analyze_scripts": {"executed_lines": [142, 144, 146, 147, 148, 149, 150, 152, 158, 159, 160, 162], "summary": {"covered_lines": 12, "num_statements": 12, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TurborepoMigrator.generate_turbo_config": {"executed_lines": [166, 168, 171, 172, 178, 179, 185, 186, 189, 194, 199, 202, 208], "summary": {"covered_lines": 13, "num_statements": 18, "percent_covered": 72.22222222222223, "percent_covered_display": "72", "missing_lines": 5, "excluded_lines": 0}, "missing_lines": [190, 191, 195, 196, 200], "excluded_lines": []}, "TurborepoMigrator._infer_build_outputs": {"executed_lines": [212, 214, 215, 218, 219, 220, 222, 223, 224, 225, 228], "summary": {"covered_lines": 11, "num_statements": 13, "percent_covered": 84.61538461538461, "percent_covered_display": "85", "missing_lines": 2, "excluded_lines": 0}, "missing_lines": [221, 226], "excluded_lines": []}, "TurborepoMigrator.update_root_package_json": {"executed_lines": [232, 234, 235, 236, 239, 240, 242, 245, 248, 249, 251, 252, 254], "summary": {"covered_lines": 13, "num_statements": 14, "percent_covered": 92.85714285714286, "percent_covered_display": "93", "missing_lines": 1, "excluded_lines": 0}, "missing_lines": [246], "excluded_lines": []}, "TurborepoMigrator.generate_migration_report": {"executed_lines": [260, 262, 263, 264, 265, 267, 268, 269, 270, 272, 273, 274, 275, 276, 278, 279, 280, 281, 282, 283, 285, 286, 287, 288, 290, 291, 292, 293, 294, 295, 296, 298, 299, 301, 302, 303, 305, 306, 308], "summary": {"covered_lines": 39, "num_statements": 39, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TurborepoMigrator.write_files": {"executed_lines": [312, 313, 314, 315, 316, 318, 321, 322, 323, 324, 327, 328, 329, 330], "summary": {"covered_lines": 14, "num_statements": 14, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TurborepoMigrator.migrate": {"executed_lines": [334, 335, 336, 337, 339, 340, 341, 343, 344, 345, 347, 348, 350, 351, 352, 354, 355, 356], "summary": {"covered_lines": 18, "num_statements": 18, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "main": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 7, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 7, "excluded_lines": 0}, "missing_lines": [361, 364, 370, 375, 382, 384, 390], "excluded_lines": []}, "": {"executed_lines": [2, 8, 9, 10, 11, 12, 13, 16, 17, 19, 39, 53, 79, 100, 115, 140, 164, 210, 230, 256, 310, 332, 359, 393], "summary": {"covered_lines": 22, "num_statements": 23, "percent_covered": 95.65217391304348, "percent_covered_display": "96", "missing_lines": 1, "excluded_lines": 0}, "missing_lines": [394], "excluded_lines": []}}, "classes": {"TurborepoMigrator": {"executed_lines": [33, 34, 35, 36, 37, 41, 42, 44, 45, 47, 48, 49, 55, 57, 58, 59, 62, 63, 67, 68, 73, 77, 81, 83, 84, 85, 93, 94, 96, 97, 98, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 117, 120, 122, 123, 124, 126, 127, 128, 130, 142, 144, 146, 147, 148, 149, 150, 152, 158, 159, 160, 162, 166, 168, 171, 172, 178, 179, 185, 186, 189, 194, 199, 202, 208, 212, 214, 215, 218, 219, 220, 222, 223, 224, 225, 228, 232, 234, 235, 236, 239, 240, 242, 245, 248, 249, 251, 252, 254, 260, 262, 263, 264, 265, 267, 268, 269, 270, 272, 273, 274, 275, 276, 278, 279, 280, 281, 282, 283, 285, 286, 287, 288, 290, 291, 292, 293, 294, 295, 296, 298, 299, 301, 302, 303, 305, 306, 308, 312, 313, 314, 315, 316, 318, 321, 322, 323, 324, 327, 328, 329, 330, 334, 335, 336, 337, 339, 340, 341, 343, 344, 345, 347, 348, 350, 351, 352, 354, 355, 356], "summary": {"covered_lines": 172, "num_statements": 183, "percent_covered": 93.98907103825137, "percent_covered_display": "94", "missing_lines": 11, "excluded_lines": 0}, "missing_lines": [86, 89, 90, 190, 191, 195, 196, 200, 221, 226, 246], "excluded_lines": []}, "": {"executed_lines": [2, 8, 9, 10, 11, 12, 13, 16, 17, 19, 39, 53, 79, 100, 115, 140, 164, 210, 230, 256, 310, 332, 359, 393], "summary": {"covered_lines": 22, "num_statements": 30, "percent_covered": 73.33333333333333, "percent_covered_display": "73", "missing_lines": 8, "excluded_lines": 0}, "missing_lines": [361, 364, 370, 375, 382, 384, 390, 394], "excluded_lines": []}}}}, "totals": {"covered_lines": 673, "num_statements": 708, "percent_covered": 95.05649717514125, "percent_covered_display": "95", "missing_lines": 35, "excluded_lines": 0}}
      • requirements.txt 52 B
        pytest>=7.0.0
        pytest-cov>=4.0.0
        pytest-mock>=3.10.0
        
      • test_nextjs_init.py 10.5 KB
        """Tests for nextjs-init.py script."""
        
        import json
        import sys
        from pathlib import Path
        
        import pytest
        
        # Add parent directory to path to import the script
        sys.path.insert(0, str(Path(__file__).parent.parent))
        
        from nextjs_init import NextJSInitializer
        
        
        class TestNextJSInitializer:
            """Test suite for NextJSInitializer."""
        
            def test_init_with_defaults(self, tmp_path):
                """Test initialization with default parameters."""
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=tmp_path / "test-app"
                )
        
                assert initializer.name == "test-app"
                assert initializer.typescript is True
                assert initializer.app_router is True
                assert initializer.src_dir is False
                assert initializer.tailwind is False
                assert initializer.eslint is True
        
            def test_validate_name_valid(self, tmp_path):
                """Test name validation with valid names."""
                valid_names = ["my-app", "my_app", "myapp123", "test-app-1"]
        
                for name in valid_names:
                    initializer = NextJSInitializer(
                        name=name,
                        directory=tmp_path / name
                    )
                    initializer.validate_name()  # Should not raise
        
            def test_validate_name_invalid(self, tmp_path):
                """Test name validation with invalid names."""
                invalid_cases = [
                    ("", ValueError, "empty"),
                    ("123app", ValueError, "starts with number"),
                    ("my app", ValueError, "contains space"),
                    ("my@app", ValueError, "contains special char"),
                ]
        
                for name, expected_error, reason in invalid_cases:
                    initializer = NextJSInitializer(
                        name=name,
                        directory=tmp_path / (name or "empty")
                    )
        
                    with pytest.raises(expected_error):
                        initializer.validate_name()
        
            def test_check_directory_exists(self, tmp_path):
                """Test directory existence check."""
                existing_dir = tmp_path / "existing"
                existing_dir.mkdir()
        
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=existing_dir
                )
        
                with pytest.raises(FileExistsError):
                    initializer.check_directory()
        
            def test_create_directory_structure_app_router(self, tmp_path):
                """Test directory structure creation with App Router."""
                project_dir = tmp_path / "test-app"
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=project_dir,
                    app_router=True
                )
        
                initializer.create_directory_structure()
        
                # Check directories
                assert (project_dir / "app").exists()
                assert (project_dir / "public").exists()
                assert (project_dir / "components").exists()
                assert (project_dir / "lib").exists()
        
                # Check App Router files
                assert (project_dir / "app" / "layout.tsx").exists()
                assert (project_dir / "app" / "page.tsx").exists()
                assert (project_dir / "app" / "globals.css").exists()
        
            def test_create_directory_structure_with_src(self, tmp_path):
                """Test directory structure with src/ directory."""
                project_dir = tmp_path / "test-app"
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=project_dir,
                    src_dir=True
                )
        
                initializer.create_directory_structure()
        
                # Check src structure
                assert (project_dir / "src" / "app").exists()
                assert (project_dir / "src" / "components").exists()
                assert (project_dir / "src" / "lib").exists()
        
            def test_package_json_generation(self, tmp_path):
                """Test package.json generation."""
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=tmp_path / "test-app",
                    typescript=True,
                    tailwind=True,
                    eslint=True
                )
        
                package_json = initializer._get_package_json()
        
                assert package_json["name"] == "test-app"
                assert package_json["version"] == "0.1.0"
                assert package_json["private"] is True
        
                # Check scripts
                assert "dev" in package_json["scripts"]
                assert "build" in package_json["scripts"]
                assert "start" in package_json["scripts"]
                assert "lint" in package_json["scripts"]
        
                # Check dependencies
                assert "next" in package_json["dependencies"]
                assert "react" in package_json["dependencies"]
                assert "react-dom" in package_json["dependencies"]
        
                # Check TypeScript dependencies
                assert "typescript" in package_json["devDependencies"]
                assert "@types/node" in package_json["devDependencies"]
                assert "@types/react" in package_json["devDependencies"]
        
                # Check Tailwind dependencies
                assert "tailwindcss" in package_json["dependencies"]
        
                # Check ESLint dependencies
                assert "eslint" in package_json["devDependencies"]
        
            def test_tsconfig_generation(self, tmp_path):
                """Test tsconfig.json generation."""
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=tmp_path / "test-app",
                    typescript=True,
                    import_alias="@/*"
                )
        
                tsconfig = initializer._get_tsconfig()
        
                assert "compilerOptions" in tsconfig
                assert tsconfig["compilerOptions"]["strict"] is True
                assert tsconfig["compilerOptions"]["jsx"] == "preserve"
                assert "@/*" in tsconfig["compilerOptions"]["paths"]
                assert "next-env.d.ts" in tsconfig["include"]
        
            def test_layout_content_typescript(self, tmp_path):
                """Test layout.tsx content generation."""
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=tmp_path / "test-app",
                    typescript=True
                )
        
                content = initializer._get_layout_content()
        
                assert "import './globals.css'" in content
                assert "export const metadata" in content
                assert "children: React.ReactNode" in content
                assert "<html lang=\"en\">" in content
        
            def test_layout_content_javascript(self, tmp_path):
                """Test layout.jsx content generation."""
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=tmp_path / "test-app",
                    typescript=False
                )
        
                content = initializer._get_layout_content()
        
                assert "import './globals.css'" in content
                assert "export const metadata" in content
                assert "React.ReactNode" not in content  # No TypeScript types
        
            def test_tailwind_config_typescript(self, tmp_path):
                """Test Tailwind config generation with TypeScript."""
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=tmp_path / "test-app",
                    typescript=True,
                    tailwind=True
                )
        
                config = initializer._get_tailwind_config()
        
                assert "import type { Config }" in config
                assert "const config: Config" in config
                assert "content:" in config
        
            def test_tailwind_config_javascript(self, tmp_path):
                """Test Tailwind config generation with JavaScript."""
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=tmp_path / "test-app",
                    typescript=False,
                    tailwind=True
                )
        
                config = initializer._get_tailwind_config()
        
                assert "module.exports" in config
                assert "content:" in config
        
            def test_gitignore_generation(self, tmp_path):
                """Test .gitignore generation."""
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=tmp_path / "test-app"
                )
        
                gitignore = initializer._get_gitignore()
        
                assert "/node_modules" in gitignore
                assert "/.next/" in gitignore
                assert ".env*.local" in gitignore
                assert ".DS_Store" in gitignore
        
            def test_readme_generation(self, tmp_path):
                """Test README.md generation."""
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=tmp_path / "test-app"
                )
        
                readme = initializer._get_readme()
        
                assert "# test-app" in readme
                assert "Next.js" in readme
                assert "npm run dev" in readme
        
            def test_create_config_files(self, tmp_path):
                """Test configuration files creation."""
                project_dir = tmp_path / "test-app"
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=project_dir,
                    typescript=True,
                    tailwind=True,
                    eslint=True
                )
        
                initializer.create_directory_structure()
                initializer.create_config_files()
        
                # Check all config files exist
                assert (project_dir / "package.json").exists()
                assert (project_dir / "next.config.js").exists()
                assert (project_dir / "tsconfig.json").exists()
                assert (project_dir / ".eslintrc.json").exists()
                assert (project_dir / "tailwind.config.ts").exists()
                assert (project_dir / "postcss.config.js").exists()
                assert (project_dir / ".gitignore").exists()
                assert (project_dir / "README.md").exists()
        
                # Verify package.json is valid JSON
                with open(project_dir / "package.json") as f:
                    package_json = json.load(f)
                    assert package_json["name"] == "test-app"
        
            def test_full_initialization(self, tmp_path):
                """Test full initialization process."""
                project_dir = tmp_path / "test-app"
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=project_dir,
                    typescript=True,
                    app_router=True,
                    tailwind=True
                )
        
                initializer.initialize()
        
                # Verify directory exists
                assert project_dir.exists()
        
                # Verify structure
                assert (project_dir / "app").exists()
                assert (project_dir / "public").exists()
        
                # Verify config files
                assert (project_dir / "package.json").exists()
                assert (project_dir / "tsconfig.json").exists()
                assert (project_dir / "next.config.js").exists()
        
            def test_pages_router_structure(self, tmp_path):
                """Test Pages Router directory structure."""
                project_dir = tmp_path / "test-app"
                initializer = NextJSInitializer(
                    name="test-app",
                    directory=project_dir,
                    app_router=False  # Use Pages Router
                )
        
                initializer.create_directory_structure()
        
                # Check Pages Router files
                assert (project_dir / "pages" / "_app.tsx").exists()
                assert (project_dir / "pages" / "index.tsx").exists()
        
      • test_turborepo_migrate.py 12.6 KB
        """Tests for turborepo-migrate.py script."""
        
        import json
        import sys
        from pathlib import Path
        
        import pytest
        
        # Add parent directory to path to import the script
        sys.path.insert(0, str(Path(__file__).parent.parent))
        
        from turborepo_migrate import TurborepoMigrator
        
        
        @pytest.fixture
        def mock_monorepo(tmp_path):
            """Create a mock monorepo structure."""
            # Root package.json
            root_pkg = {
                "name": "test-monorepo",
                "private": True,
                "workspaces": ["apps/*", "packages/*"],
                "scripts": {
                    "build": "npm run build --workspaces",
                    "test": "npm run test --workspaces"
                }
            }
        
            (tmp_path / "package.json").write_text(json.dumps(root_pkg, indent=2))
        
            # Create apps
            apps_dir = tmp_path / "apps"
            apps_dir.mkdir()
        
            web_dir = apps_dir / "web"
            web_dir.mkdir()
            (web_dir / "package.json").write_text(json.dumps({
                "name": "web",
                "version": "1.0.0",
                "scripts": {
                    "dev": "next dev",
                    "build": "next build",
                    "test": "jest",
                    "lint": "eslint ."
                },
                "dependencies": {
                    "@repo/ui": "*",
                    "next": "latest"
                }
            }, indent=2))
        
            # Create Next.js output directory
            (web_dir / ".next").mkdir()
        
            # Create packages
            packages_dir = tmp_path / "packages"
            packages_dir.mkdir()
        
            ui_dir = packages_dir / "ui"
            ui_dir.mkdir()
            (ui_dir / "package.json").write_text(json.dumps({
                "name": "@repo/ui",
                "version": "0.0.0",
                "scripts": {
                    "build": "tsc",
                    "test": "jest",
                    "lint": "eslint ."
                },
                "dependencies": {
                    "react": "latest"
                }
            }, indent=2))
        
            # Create dist directory
            (ui_dir / "dist").mkdir()
        
            return tmp_path
        
        
        class TestTurborepoMigrator:
            """Test suite for TurborepoMigrator."""
        
            def test_init(self, tmp_path):
                """Test migrator initialization."""
                migrator = TurborepoMigrator(
                    path=tmp_path,
                    dry_run=True,
                    package_manager="npm"
                )
        
                assert migrator.path == tmp_path.resolve()
                assert migrator.dry_run is True
                assert migrator.package_manager == "npm"
        
            def test_validate_path_exists(self, mock_monorepo):
                """Test path validation with valid monorepo."""
                migrator = TurborepoMigrator(path=mock_monorepo)
                migrator.validate_path()  # Should not raise
        
            def test_validate_path_not_exists(self, tmp_path):
                """Test path validation with non-existent path."""
                migrator = TurborepoMigrator(path=tmp_path / "nonexistent")
        
                with pytest.raises(FileNotFoundError):
                    migrator.validate_path()
        
            def test_validate_path_not_directory(self, tmp_path):
                """Test path validation with file instead of directory."""
                file_path = tmp_path / "file.txt"
                file_path.touch()
        
                migrator = TurborepoMigrator(path=file_path)
        
                with pytest.raises(NotADirectoryError):
                    migrator.validate_path()
        
            def test_validate_path_no_package_json(self, tmp_path):
                """Test path validation without package.json."""
                empty_dir = tmp_path / "empty"
                empty_dir.mkdir()
        
                migrator = TurborepoMigrator(path=empty_dir)
        
                with pytest.raises(FileNotFoundError):
                    migrator.validate_path()
        
            def test_analyze_workspace_npm(self, mock_monorepo):
                """Test workspace analysis for npm/yarn workspaces."""
                migrator = TurborepoMigrator(path=mock_monorepo)
                migrator.analyze_workspace()
        
                assert migrator.workspace_config["type"] == "npm/yarn"
                assert "apps/*" in migrator.workspace_config["patterns"]
                assert "packages/*" in migrator.workspace_config["patterns"]
        
            def test_analyze_workspace_pnpm(self, tmp_path):
                """Test workspace analysis for pnpm workspaces."""
                # Create root package.json without workspaces
                (tmp_path / "package.json").write_text(json.dumps({
                    "name": "test-monorepo",
                    "private": True
                }))
        
                # Create pnpm-workspace.yaml
                (tmp_path / "pnpm-workspace.yaml").write_text("""packages:
          - 'apps/*'
          - 'packages/*'
        """)
        
                migrator = TurborepoMigrator(path=tmp_path)
                migrator.analyze_workspace()
        
                assert migrator.workspace_config["type"] == "pnpm"
                assert migrator.workspace_config["file"] == "pnpm-workspace.yaml"
        
            def test_discover_packages(self, mock_monorepo):
                """Test package discovery."""
                migrator = TurborepoMigrator(path=mock_monorepo)
                migrator.analyze_workspace()
                migrator.discover_packages()
        
                assert len(migrator.packages) == 2
        
                package_names = {pkg["name"] for pkg in migrator.packages}
                assert "web" in package_names
                assert "@repo/ui" in package_names
        
            def test_analyze_scripts(self, mock_monorepo):
                """Test script analysis."""
                migrator = TurborepoMigrator(path=mock_monorepo)
                migrator.analyze_workspace()
                migrator.discover_packages()
        
                common_scripts = migrator.analyze_scripts()
        
                # All packages have build, test, lint
                assert "build" in common_scripts
                assert "test" in common_scripts
                assert "lint" in common_scripts
        
                # Check package counts
                assert len(common_scripts["build"]) == 2
                assert len(common_scripts["test"]) == 2
        
            def test_infer_build_outputs(self, mock_monorepo):
                """Test build output inference."""
                migrator = TurborepoMigrator(path=mock_monorepo)
                migrator.analyze_workspace()
                migrator.discover_packages()
        
                outputs = migrator._infer_build_outputs()
        
                # Should detect .next and dist directories
                assert ".next/**" in outputs
                assert "!.next/cache/**" in outputs
                assert "dist/**" in outputs
        
            def test_generate_turbo_config(self, mock_monorepo):
                """Test turbo.json generation."""
                migrator = TurborepoMigrator(path=mock_monorepo)
                migrator.analyze_workspace()
                migrator.discover_packages()
        
                common_scripts = migrator.analyze_scripts()
                turbo_config = migrator.generate_turbo_config(common_scripts)
        
                assert "$schema" in turbo_config
                assert "pipeline" in turbo_config
        
                # Check build task
                assert "build" in turbo_config["pipeline"]
                assert turbo_config["pipeline"]["build"]["dependsOn"] == ["^build"]
                assert "outputs" in turbo_config["pipeline"]["build"]
        
                # Check test task
                assert "test" in turbo_config["pipeline"]
                assert "coverage/**" in turbo_config["pipeline"]["test"]["outputs"]
        
                # Check lint task
                assert "lint" in turbo_config["pipeline"]
        
                # Note: dev task won't be in pipeline because it's only in 1 package
                # (needs to be in 2+ packages to be considered "common")
                # This is correct behavior - only truly common scripts are included
        
            def test_update_root_package_json(self, mock_monorepo):
                """Test root package.json update."""
                migrator = TurborepoMigrator(path=mock_monorepo)
                migrator.analyze_workspace()
                migrator.discover_packages()
        
                updated_package_json = migrator.update_root_package_json()
        
                # Check turbo added to devDependencies
                assert "turbo" in updated_package_json["devDependencies"]
                assert updated_package_json["devDependencies"]["turbo"] == "latest"
        
                # Check scripts updated (only common scripts are added)
                assert updated_package_json["scripts"]["build"] == "turbo run build"
                assert updated_package_json["scripts"]["test"] == "turbo run test"
                assert updated_package_json["scripts"]["lint"] == "turbo run lint"
                # dev is only in one package, so it won't be added
        
            def test_generate_migration_report(self, mock_monorepo):
                """Test migration report generation."""
                migrator = TurborepoMigrator(path=mock_monorepo)
                migrator.analyze_workspace()
                migrator.discover_packages()
        
                common_scripts = migrator.analyze_scripts()
                turbo_config = migrator.generate_turbo_config(common_scripts)
                updated_package_json = migrator.update_root_package_json()
        
                report = migrator.generate_migration_report(turbo_config, updated_package_json)
        
                assert "TURBOREPO MIGRATION REPORT" in report
                assert "PACKAGES:" in report
                assert "TURBO.JSON PIPELINE:" in report
                assert "ROOT PACKAGE.JSON SCRIPTS:" in report
                assert "RECOMMENDATIONS:" in report
        
                # Check package names appear
                assert "web" in report
                assert "@repo/ui" in report
        
            def test_write_files_dry_run(self, mock_monorepo, capsys):
                """Test file writing in dry-run mode."""
                migrator = TurborepoMigrator(path=mock_monorepo, dry_run=True)
                migrator.analyze_workspace()
                migrator.discover_packages()
        
                common_scripts = migrator.analyze_scripts()
                turbo_config = migrator.generate_turbo_config(common_scripts)
                updated_package_json = migrator.update_root_package_json()
        
                migrator.write_files(turbo_config, updated_package_json)
        
                # Check files not created
                assert not (mock_monorepo / "turbo.json").exists()
        
                # Check output
                captured = capsys.readouterr()
                assert "DRY RUN" in captured.out
        
            def test_write_files_actual(self, mock_monorepo):
                """Test actual file writing."""
                migrator = TurborepoMigrator(path=mock_monorepo, dry_run=False)
                migrator.analyze_workspace()
                migrator.discover_packages()
        
                common_scripts = migrator.analyze_scripts()
                turbo_config = migrator.generate_turbo_config(common_scripts)
                updated_package_json = migrator.update_root_package_json()
        
                migrator.write_files(turbo_config, updated_package_json)
        
                # Check turbo.json created
                assert (mock_monorepo / "turbo.json").exists()
        
                # Verify content
                with open(mock_monorepo / "turbo.json") as f:
                    saved_config = json.load(f)
                    assert saved_config["$schema"] == turbo_config["$schema"]
                    assert "pipeline" in saved_config
        
                # Check package.json updated
                with open(mock_monorepo / "package.json") as f:
                    saved_package = json.load(f)
                    assert "turbo" in saved_package["devDependencies"]
        
            def test_full_migration_dry_run(self, mock_monorepo):
                """Test full migration process in dry-run mode."""
                migrator = TurborepoMigrator(path=mock_monorepo, dry_run=True)
                migrator.migrate()
        
                # Files should not be created in dry-run
                assert not (mock_monorepo / "turbo.json").exists()
        
                # Original package.json should be unchanged
                with open(mock_monorepo / "package.json") as f:
                    package_json = json.load(f)
                    assert "turbo" not in package_json.get("devDependencies", {})
        
            def test_full_migration_actual(self, mock_monorepo):
                """Test full migration process."""
                migrator = TurborepoMigrator(path=mock_monorepo, dry_run=False)
                migrator.migrate()
        
                # Check turbo.json created
                assert (mock_monorepo / "turbo.json").exists()
        
                with open(mock_monorepo / "turbo.json") as f:
                    turbo_config = json.load(f)
                    assert "$schema" in turbo_config
                    assert "pipeline" in turbo_config
                    assert "build" in turbo_config["pipeline"]
        
                # Check package.json updated
                with open(mock_monorepo / "package.json") as f:
                    package_json = json.load(f)
                    assert "turbo" in package_json["devDependencies"]
                    assert package_json["scripts"]["build"] == "turbo run build"
        
            def test_parse_pnpm_workspace(self, tmp_path):
                """Test pnpm-workspace.yaml parsing."""
                yaml_content = """packages:
          - 'apps/*'
          - 'packages/*'
          - 'tools/*'
        """
                yaml_file = tmp_path / "pnpm-workspace.yaml"
                yaml_file.write_text(yaml_content)
        
                migrator = TurborepoMigrator(path=tmp_path)
                patterns = migrator._parse_pnpm_workspace(yaml_file)
        
                assert len(patterns) == 3
                assert "apps/*" in patterns
                assert "packages/*" in patterns
                assert "tools/*" in patterns
        
            def test_monorepo_without_workspaces(self, tmp_path):
                """Test migration fails for non-workspace monorepo."""
                # Create package.json without workspaces
                (tmp_path / "package.json").write_text(json.dumps({
                    "name": "not-a-monorepo",
                    "version": "1.0.0"
                }))
        
                migrator = TurborepoMigrator(path=tmp_path)
        
                # migrate() calls sys.exit(1) on error, so we catch SystemExit
                with pytest.raises(SystemExit):
                    migrator.migrate()
        
    • nextjs_init.py 14.5 KB
      #!/usr/bin/env python3
      """
      Next.js Project Initialization Script
      
      Initialize new Next.js project with best practices, TypeScript, and optimized configuration.
      """
      
      import argparse
      import json
      import os
      import subprocess
      import sys
      from pathlib import Path
      from typing import Optional
      
      
      class NextJSInitializer:
          """Initialize Next.js project with best practices."""
      
          def __init__(
              self,
              name: str,
              directory: Optional[Path] = None,
              typescript: bool = True,
              app_router: bool = True,
              src_dir: bool = False,
              tailwind: bool = False,
              eslint: bool = True,
              import_alias: str = "@/*",
          ):
              """
              Initialize NextJSInitializer.
      
              Args:
                  name: Project name
                  directory: Target directory (default: current directory / name)
                  typescript: Enable TypeScript
                  app_router: Use App Router (recommended)
                  src_dir: Use src/ directory
                  tailwind: Include Tailwind CSS
                  eslint: Include ESLint
                  import_alias: Import alias pattern
              """
              self.name = name
              self.directory = directory or Path.cwd() / name
              self.typescript = typescript
              self.app_router = app_router
              self.src_dir = src_dir
              self.tailwind = tailwind
              self.eslint = eslint
              self.import_alias = import_alias
      
          def validate_name(self) -> None:
              """Validate project name."""
              if not self.name:
                  raise ValueError("Project name cannot be empty")
      
              if not self.name.replace("-", "").replace("_", "").isalnum():
                  raise ValueError(
                      "Project name can only contain letters, numbers, hyphens, and underscores"
                  )
      
              if self.name[0].isdigit():
                  raise ValueError("Project name cannot start with a number")
      
          def check_directory(self) -> None:
              """Check if target directory exists."""
              if self.directory.exists():
                  raise FileExistsError(f"Directory '{self.directory}' already exists")
      
          def create_directory_structure(self) -> None:
              """Create project directory structure."""
              print(f"Creating directory structure in {self.directory}...")
      
              # Create base directories
              self.directory.mkdir(parents=True, exist_ok=True)
      
              # Determine app/pages directory location
              base_dir = self.directory / "src" if self.src_dir else self.directory
      
              if self.app_router:
                  app_dir = base_dir / "app"
                  app_dir.mkdir(parents=True, exist_ok=True)
                  (app_dir / "favicon.ico").touch()
                  self._create_app_router_files(app_dir)
              else:
                  pages_dir = base_dir / "pages"
                  pages_dir.mkdir(parents=True, exist_ok=True)
                  self._create_pages_router_files(pages_dir)
      
              # Create additional directories
              (self.directory / "public").mkdir(exist_ok=True)
              (base_dir / "components").mkdir(parents=True, exist_ok=True)
              (base_dir / "lib").mkdir(parents=True, exist_ok=True)
      
          def _create_app_router_files(self, app_dir: Path) -> None:
              """Create App Router files."""
              ext = "tsx" if self.typescript else "jsx"
      
              # Create layout
              layout_content = self._get_layout_content()
              (app_dir / f"layout.{ext}").write_text(layout_content)
      
              # Create page
              page_content = self._get_page_content()
              (app_dir / f"page.{ext}").write_text(page_content)
      
              # Create global styles
              if self.tailwind:
                  globals_content = self._get_tailwind_globals()
              else:
                  globals_content = self._get_basic_globals()
              (app_dir / "globals.css").write_text(globals_content)
      
          def _create_pages_router_files(self, pages_dir: Path) -> None:
              """Create Pages Router files."""
              ext = "tsx" if self.typescript else "jsx"
      
              # Create _app
              app_content = self._get_app_content()
              (pages_dir / f"_app.{ext}").write_text(app_content)
      
              # Create index
              index_content = self._get_index_content()
              (pages_dir / f"index.{ext}").write_text(index_content)
      
          def create_config_files(self) -> None:
              """Create configuration files."""
              print("Creating configuration files...")
      
              # package.json
              package_json = self._get_package_json()
              (self.directory / "package.json").write_text(
                  json.dumps(package_json, indent=2)
              )
      
              # next.config.js
              next_config = self._get_next_config()
              (self.directory / "next.config.js").write_text(next_config)
      
              # tsconfig.json
              if self.typescript:
                  tsconfig = self._get_tsconfig()
                  (self.directory / "tsconfig.json").write_text(
                      json.dumps(tsconfig, indent=2)
                  )
      
              # .eslintrc.json
              if self.eslint:
                  eslint_config = self._get_eslint_config()
                  (self.directory / ".eslintrc.json").write_text(
                      json.dumps(eslint_config, indent=2)
                  )
      
              # tailwind.config
              if self.tailwind:
                  tailwind_config = self._get_tailwind_config()
                  ext = "ts" if self.typescript else "js"
                  (self.directory / f"tailwind.config.{ext}").write_text(tailwind_config)
      
                  postcss_config = self._get_postcss_config()
                  (self.directory / "postcss.config.js").write_text(postcss_config)
      
              # .gitignore
              gitignore = self._get_gitignore()
              (self.directory / ".gitignore").write_text(gitignore)
      
              # README.md
              readme = self._get_readme()
              (self.directory / "README.md").write_text(readme)
      
          def _get_package_json(self) -> dict:
              """Generate package.json content."""
              dependencies = {
                  "next": "latest",
                  "react": "latest",
                  "react-dom": "latest",
              }
      
              dev_dependencies = {}
      
              if self.typescript:
                  dev_dependencies.update(
                      {
                          "typescript": "^5.0.0",
                          "@types/node": "^20.0.0",
                          "@types/react": "^18.0.0",
                          "@types/react-dom": "^18.0.0",
                      }
                  )
      
              if self.eslint:
                  dev_dependencies["eslint"] = "^8.0.0"
                  dev_dependencies["eslint-config-next"] = "latest"
      
              if self.tailwind:
                  dependencies["tailwindcss"] = "^3.3.0"
                  dependencies["autoprefixer"] = "^10.0.0"
                  dependencies["postcss"] = "^8.0.0"
      
              return {
                  "name": self.name,
                  "version": "0.1.0",
                  "private": True,
                  "scripts": {
                      "dev": "next dev",
                      "build": "next build",
                      "start": "next start",
                      "lint": "next lint" if self.eslint else None,
                  },
                  "dependencies": dependencies,
                  "devDependencies": dev_dependencies,
              }
      
          def _get_layout_content(self) -> str:
              """Generate layout.tsx content."""
              import_css = (
                  "import './globals.css'\n" if not self.tailwind else "import './globals.css'\n"
              )
      
              if self.typescript:
                  return f"""{import_css}
      export const metadata = {{
        title: '{self.name}',
        description: 'Generated by Next.js',
      }}
      
      export default function RootLayout({{
        children,
      }}: {{
        children: React.ReactNode
      }}) {{
        return (
          <html lang="en">
            <body>{{children}}</body>
          </html>
        )
      }}
      """
              return f"""{import_css}
      export const metadata = {{
        title: '{self.name}',
        description: 'Generated by Next.js',
      }}
      
      export default function RootLayout({{ children }}) {{
        return (
          <html lang="en">
            <body>{{children}}</body>
          </html>
        )
      }}
      """
      
          def _get_page_content(self) -> str:
              """Generate page.tsx content."""
              return """export default function Home() {
        return (
          <main>
            <h1>Welcome to Next.js!</h1>
            <p>Get started by editing this page.</p>
          </main>
        )
      }
      """
      
          def _get_next_config(self) -> str:
              """Generate next.config.js content."""
              return """/** @type {import('next').NextConfig} */
      const nextConfig = {
        reactStrictMode: true,
        images: {
          remotePatterns: [
            // Add your image domains here
          ],
        },
      }
      
      module.exports = nextConfig
      """
      
          def _get_tsconfig(self) -> dict:
              """Generate tsconfig.json content."""
              return {
                  "compilerOptions": {
                      "target": "ES2020",
                      "lib": ["dom", "dom.iterable", "esnext"],
                      "allowJs": True,
                      "skipLibCheck": True,
                      "strict": True,
                      "noEmit": True,
                      "esModuleInterop": True,
                      "module": "esnext",
                      "moduleResolution": "bundler",
                      "resolveJsonModule": True,
                      "isolatedModules": True,
                      "jsx": "preserve",
                      "incremental": True,
                      "plugins": [{"name": "next"}],
                      "paths": {self.import_alias: ["./*"]},
                  },
                  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
                  "exclude": ["node_modules"],
              }
      
          def _get_eslint_config(self) -> dict:
              """Generate .eslintrc.json content."""
              return {"extends": "next/core-web-vitals"}
      
          def _get_tailwind_config(self) -> str:
              """Generate tailwind.config content."""
              if self.typescript:
                  return """import type { Config } from 'tailwindcss'
      
      const config: Config = {
        content: [
          './pages/**/*.{js,ts,jsx,tsx,mdx}',
          './components/**/*.{js,ts,jsx,tsx,mdx}',
          './app/**/*.{js,ts,jsx,tsx,mdx}',
        ],
        theme: {
          extend: {},
        },
        plugins: [],
      }
      export default config
      """
              return """/** @type {import('tailwindcss').Config} */
      module.exports = {
        content: [
          './pages/**/*.{js,ts,jsx,tsx,mdx}',
          './components/**/*.{js,ts,jsx,tsx,mdx}',
          './app/**/*.{js,ts,jsx,tsx,mdx}',
        ],
        theme: {
          extend: {},
        },
        plugins: [],
      }
      """
      
          def _get_postcss_config(self) -> str:
              """Generate postcss.config.js content."""
              return """module.exports = {
        plugins: {
          tailwindcss: {},
          autoprefixer: {},
        },
      }
      """
      
          def _get_tailwind_globals(self) -> str:
              """Generate globals.css with Tailwind."""
              return """@tailwind base;
      @tailwind components;
      @tailwind utilities;
      """
      
          def _get_basic_globals(self) -> str:
              """Generate basic globals.css."""
              return """* {
        box-sizing: border-box;
        padding: 0;
        margin: 0;
      }
      
      html,
      body {
        max-width: 100vw;
        overflow-x: hidden;
      }
      
      a {
        color: inherit;
        text-decoration: none;
      }
      """
      
          def _get_gitignore(self) -> str:
              """Generate .gitignore content."""
              return """# dependencies
      /node_modules
      /.pnp
      .pnp.js
      
      # testing
      /coverage
      
      # next.js
      /.next/
      /out/
      
      # production
      /build
      
      # misc
      .DS_Store
      *.pem
      
      # debug
      npm-debug.log*
      yarn-debug.log*
      yarn-error.log*
      
      # local env files
      .env*.local
      
      # vercel
      .vercel
      
      # typescript
      *.tsbuildinfo
      next-env.d.ts
      """
      
          def _get_readme(self) -> str:
              """Generate README.md content."""
              return f"""# {self.name}
      
      This is a [Next.js](https://nextjs.org/) project bootstrapped with next.js initialization script.
      
      ## Getting Started
      
      First, install dependencies:
      
      ```bash
      npm install
      # or
      yarn install
      # or
      pnpm install
      ```
      
      Then, run the development server:
      
      ```bash
      npm run dev
      # or
      yarn dev
      # or
      pnpm dev
      ```
      
      Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
      
      ## Learn More
      
      To learn more about Next.js, take a look at the following resources:
      
      - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
      - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
      
      ## Deploy on Vercel
      
      The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new).
      
      Check out the [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
      """
      
          def _get_app_content(self) -> str:
              """Generate _app content for Pages Router."""
              return """export default function App({ Component, pageProps }) {
        return <Component {...pageProps} />
      }
      """
      
          def _get_index_content(self) -> str:
              """Generate index content for Pages Router."""
              return """export default function Home() {
        return (
          <main>
            <h1>Welcome to Next.js!</h1>
            <p>Get started by editing this page.</p>
          </main>
        )
      }
      """
      
          def initialize(self) -> None:
              """Run full initialization process."""
              try:
                  print(f"Initializing Next.js project: {self.name}")
                  print(f"TypeScript: {self.typescript}")
                  print(f"App Router: {self.app_router}")
                  print(f"Tailwind CSS: {self.tailwind}")
                  print(f"ESLint: {self.eslint}")
                  print()
      
                  self.validate_name()
                  self.check_directory()
                  self.create_directory_structure()
                  self.create_config_files()
      
                  print()
                  print(f"✓ Project initialized successfully!")
                  print()
                  print(f"Next steps:")
                  print(f"  cd {self.name}")
                  print(f"  npm install")
                  print(f"  npm run dev")
                  print()
      
              except Exception as e:
                  print(f"Error: {e}", file=sys.stderr)
                  sys.exit(1)
      
      
      def main():
          """CLI entry point."""
          parser = argparse.ArgumentParser(
              description="Initialize Next.js project with best practices"
          )
          parser.add_argument("name", help="Project name")
          parser.add_argument(
              "--directory", type=Path, help="Target directory (default: ./<name>)"
          )
          parser.add_argument(
              "--no-typescript", action="store_true", help="Disable TypeScript"
          )
          parser.add_argument(
              "--pages-router", action="store_true", help="Use Pages Router instead of App Router"
          )
          parser.add_argument("--src-dir", action="store_true", help="Use src/ directory")
          parser.add_argument("--tailwind", action="store_true", help="Include Tailwind CSS")
          parser.add_argument("--no-eslint", action="store_true", help="Disable ESLint")
          parser.add_argument(
              "--import-alias", default="@/*", help="Import alias pattern (default: @/*)"
          )
      
          args = parser.parse_args()
      
          initializer = NextJSInitializer(
              name=args.name,
              directory=args.directory,
              typescript=not args.no_typescript,
              app_router=not args.pages_router,
              src_dir=args.src_dir,
              tailwind=args.tailwind,
              eslint=not args.no_eslint,
              import_alias=args.import_alias,
          )
      
          initializer.initialize()
      
      
      if __name__ == "__main__":
          main()
      
    • requirements.txt 430 B
      # Web Frameworks Skill Dependencies
      # Python 3.10+ required
      
      # No Python package dependencies - uses only standard library
      
      # Testing dependencies (dev)
      pytest>=8.0.0
      pytest-cov>=4.1.0
      pytest-mock>=3.12.0
      
      # Note: This skill works with Node.js frameworks
      # Requires Node.js and package managers:
      #   - Node.js 18+: https://nodejs.org/
      #   - npm (comes with Node.js)
      #   - pnpm: npm install -g pnpm
      #   - yarn: npm install -g yarn
      
    • turborepo_migrate.py 12.9 KB
      #!/usr/bin/env python3
      """
      Turborepo Migration Script
      
      Convert existing monorepo to Turborepo with intelligent pipeline generation.
      """
      
      import argparse
      import json
      import os
      import sys
      from pathlib import Path
      from typing import Dict, List, Optional, Set
      
      
      class TurborepoMigrator:
          """Migrate existing monorepo to Turborepo."""
      
          def __init__(
              self,
              path: Path,
              dry_run: bool = False,
              package_manager: str = "npm",
          ):
              """
              Initialize TurborepoMigrator.
      
              Args:
                  path: Path to existing monorepo
                  dry_run: Preview changes without writing files
                  package_manager: Package manager (npm, yarn, pnpm, bun)
              """
              self.path = path.resolve()
              self.dry_run = dry_run
              self.package_manager = package_manager
              self.packages: List[Dict] = []
              self.workspace_config: Dict = {}
      
          def validate_path(self) -> None:
              """Validate monorepo path."""
              if not self.path.exists():
                  raise FileNotFoundError(f"Path '{self.path}' does not exist")
      
              if not self.path.is_dir():
                  raise NotADirectoryError(f"Path '{self.path}' is not a directory")
      
              package_json = self.path / "package.json"
              if not package_json.exists():
                  raise FileNotFoundError(
                      f"No package.json found in '{self.path}'. Not a valid monorepo."
                  )
      
          def analyze_workspace(self) -> None:
              """Analyze existing workspace configuration."""
              print("Analyzing workspace...")
      
              package_json = self.path / "package.json"
              with open(package_json) as f:
                  root_config = json.load(f)
      
              # Detect workspace configuration
              if "workspaces" in root_config:
                  self.workspace_config = {
                      "type": "npm/yarn",
                      "patterns": root_config["workspaces"],
                  }
              elif (self.path / "pnpm-workspace.yaml").exists():
                  self.workspace_config = {
                      "type": "pnpm",
                      "file": "pnpm-workspace.yaml",
                  }
              else:
                  raise ValueError(
                      "No workspace configuration found. Monorepo structure not detected."
                  )
      
              print(f"  Workspace type: {self.workspace_config['type']}")
      
          def discover_packages(self) -> None:
              """Discover all packages in workspace."""
              print("Discovering packages...")
      
              if self.workspace_config["type"] == "npm/yarn":
                  patterns = self.workspace_config["patterns"]
                  if isinstance(patterns, dict):
                      patterns = patterns.get("packages", [])
              else:
                  # Parse pnpm-workspace.yaml
                  yaml_file = self.path / "pnpm-workspace.yaml"
                  patterns = self._parse_pnpm_workspace(yaml_file)
      
              # Find all packages matching patterns
              for pattern in patterns:
                  self._find_packages_by_pattern(pattern)
      
              print(f"  Found {len(self.packages)} packages")
              for pkg in self.packages:
                  print(f"    - {pkg['name']} ({pkg['path'].relative_to(self.path)})")
      
          def _parse_pnpm_workspace(self, yaml_file: Path) -> List[str]:
              """Parse pnpm-workspace.yaml file."""
              patterns = []
              with open(yaml_file) as f:
                  in_packages = False
                  for line in f:
                      line = line.strip()
                      if line.startswith("packages:"):
                          in_packages = True
                          continue
                      if in_packages and line.startswith("- "):
                          pattern = line[2:].strip().strip("'\"")
                          patterns.append(pattern)
              return patterns
      
          def _find_packages_by_pattern(self, pattern: str) -> None:
              """Find packages matching glob pattern."""
              import glob
      
              # Convert pattern to absolute path
              search_pattern = str(self.path / pattern)
      
              for match in glob.glob(search_pattern):
                  match_path = Path(match)
                  package_json = match_path / "package.json"
      
                  if package_json.exists():
                      with open(package_json) as f:
                          pkg_data = json.load(f)
      
                      self.packages.append(
                          {
                              "name": pkg_data.get("name", match_path.name),
                              "path": match_path,
                              "scripts": pkg_data.get("scripts", {}),
                              "dependencies": pkg_data.get("dependencies", {}),
                              "devDependencies": pkg_data.get("devDependencies", {}),
                          }
                      )
      
          def analyze_scripts(self) -> Dict[str, Set[str]]:
              """Analyze common scripts across packages."""
              print("Analyzing scripts...")
      
              script_map: Dict[str, Set[str]] = {}
      
              for pkg in self.packages:
                  for script_name in pkg["scripts"]:
                      if script_name not in script_map:
                          script_map[script_name] = set()
                      script_map[script_name].add(pkg["name"])
      
              common_scripts = {
                  name: packages
                  for name, packages in script_map.items()
                  if len(packages) >= 2  # Present in at least 2 packages
              }
      
              print(f"  Found {len(common_scripts)} common scripts:")
              for script, packages in common_scripts.items():
                  print(f"    - {script} ({len(packages)} packages)")
      
              return common_scripts
      
          def generate_turbo_config(self, common_scripts: Dict[str, Set[str]]) -> Dict:
              """Generate turbo.json configuration."""
              print("Generating turbo.json configuration...")
      
              pipeline = {}
      
              # Build task
              if "build" in common_scripts:
                  pipeline["build"] = {
                      "dependsOn": ["^build"],
                      "outputs": self._infer_build_outputs(),
                  }
      
              # Test task
              if "test" in common_scripts:
                  pipeline["test"] = {
                      "dependsOn": ["build"],
                      "outputs": ["coverage/**"],
                  }
      
              # Lint task
              if "lint" in common_scripts:
                  pipeline["lint"] = {"dependsOn": ["^build"]}
      
              # Typecheck task
              if "typecheck" in common_scripts or "type-check" in common_scripts:
                  task_name = "typecheck" if "typecheck" in common_scripts else "type-check"
                  pipeline[task_name] = {"dependsOn": ["^build"]}
      
              # Dev task
              if "dev" in common_scripts or "start" in common_scripts:
                  dev_task = "dev" if "dev" in common_scripts else "start"
                  pipeline[dev_task] = {"cache": False, "persistent": True}
      
              # Clean task
              if "clean" in common_scripts:
                  pipeline["clean"] = {"cache": False}
      
              turbo_config = {
                  "$schema": "https://turbo.build/schema.json",
                  "globalDependencies": ["**/.env.*local"],
                  "pipeline": pipeline,
              }
      
              return turbo_config
      
          def _infer_build_outputs(self) -> List[str]:
              """Infer build output directories from packages."""
              outputs = set()
      
              for pkg in self.packages:
                  pkg_path = pkg["path"]
      
                  # Check common output directories
                  if (pkg_path / "dist").exists():
                      outputs.add("dist/**")
                  if (pkg_path / "build").exists():
                      outputs.add("build/**")
                  if (pkg_path / ".next").exists():
                      outputs.add(".next/**")
                      outputs.add("!.next/cache/**")
                  if (pkg_path / "out").exists():
                      outputs.add("out/**")
      
              return sorted(list(outputs)) or ["dist/**"]
      
          def update_root_package_json(self) -> Dict:
              """Update root package.json with Turborepo scripts."""
              print("Updating root package.json...")
      
              package_json_path = self.path / "package.json"
              with open(package_json_path) as f:
                  package_json = json.load(f)
      
              # Add turbo to devDependencies
              if "devDependencies" not in package_json:
                  package_json["devDependencies"] = {}
      
              package_json["devDependencies"]["turbo"] = "latest"
      
              # Update scripts to use turbo
              if "scripts" not in package_json:
                  package_json["scripts"] = {}
      
              common_tasks = ["build", "dev", "test", "lint", "typecheck", "clean"]
              for task in common_tasks:
                  # Check if task exists in any package
                  if any(task in pkg["scripts"] for pkg in self.packages):
                      package_json["scripts"][task] = f"turbo run {task}"
      
              return package_json
      
          def generate_migration_report(
              self, turbo_config: Dict, updated_package_json: Dict
          ) -> str:
              """Generate migration report."""
              report = []
      
              report.append("=" * 60)
              report.append("TURBOREPO MIGRATION REPORT")
              report.append("=" * 60)
              report.append("")
      
              report.append(f"Monorepo Path: {self.path}")
              report.append(f"Package Manager: {self.package_manager}")
              report.append(f"Total Packages: {len(self.packages)}")
              report.append("")
      
              report.append("PACKAGES:")
              for pkg in self.packages:
                  rel_path = pkg["path"].relative_to(self.path)
                  report.append(f"  - {pkg['name']} ({rel_path})")
              report.append("")
      
              report.append("TURBO.JSON PIPELINE:")
              for task, config in turbo_config["pipeline"].items():
                  report.append(f"  {task}:")
                  for key, value in config.items():
                      report.append(f"    {key}: {value}")
              report.append("")
      
              report.append("ROOT PACKAGE.JSON SCRIPTS:")
              for script, command in updated_package_json.get("scripts", {}).items():
                  report.append(f"  {script}: {command}")
              report.append("")
      
              report.append("RECOMMENDATIONS:")
              report.append("  1. Review generated turbo.json pipeline configuration")
              report.append("  2. Adjust output directories based on your build tools")
              report.append("  3. Configure remote caching: turbo login && turbo link")
              report.append("  4. Run 'npm install' to install Turborepo")
              report.append("  5. Test with: turbo run build --dry-run")
              report.append("")
      
              if self.dry_run:
                  report.append("DRY RUN MODE: No files were modified")
              else:
                  report.append("FILES CREATED/MODIFIED:")
                  report.append(f"  - {self.path / 'turbo.json'}")
                  report.append(f"  - {self.path / 'package.json'}")
      
              report.append("")
              report.append("=" * 60)
      
              return "\n".join(report)
      
          def write_files(self, turbo_config: Dict, updated_package_json: Dict) -> None:
              """Write configuration files."""
              if self.dry_run:
                  print("\nDRY RUN - Files that would be created/modified:")
                  print(f"  - {self.path / 'turbo.json'}")
                  print(f"  - {self.path / 'package.json'}")
                  return
      
              print("Writing files...")
      
              # Write turbo.json
              turbo_json_path = self.path / "turbo.json"
              with open(turbo_json_path, "w") as f:
                  json.dump(turbo_config, f, indent=2)
              print(f"  ✓ Created {turbo_json_path}")
      
              # Write updated package.json
              package_json_path = self.path / "package.json"
              with open(package_json_path, "w") as f:
                  json.dump(updated_package_json, f, indent=2)
              print(f"  ✓ Updated {package_json_path}")
      
          def migrate(self) -> None:
              """Run migration process."""
              try:
                  print(f"Migrating monorepo to Turborepo: {self.path}")
                  print(f"Dry run: {self.dry_run}")
                  print()
      
                  self.validate_path()
                  self.analyze_workspace()
                  self.discover_packages()
      
                  common_scripts = self.analyze_scripts()
                  turbo_config = self.generate_turbo_config(common_scripts)
                  updated_package_json = self.update_root_package_json()
      
                  print()
                  self.write_files(turbo_config, updated_package_json)
      
                  print()
                  report = self.generate_migration_report(turbo_config, updated_package_json)
                  print(report)
      
              except Exception as e:
                  print(f"Error: {e}", file=sys.stderr)
                  sys.exit(1)
      
      
      def main():
          """CLI entry point."""
          parser = argparse.ArgumentParser(
              description="Migrate existing monorepo to Turborepo"
          )
          parser.add_argument(
              "--path",
              type=Path,
              default=Path.cwd(),
              help="Path to monorepo (default: current directory)",
          )
          parser.add_argument(
              "--dry-run",
              action="store_true",
              help="Preview changes without writing files",
          )
          parser.add_argument(
              "--package-manager",
              choices=["npm", "yarn", "pnpm", "bun"],
              default="npm",
              help="Package manager (default: npm)",
          )
      
          args = parser.parse_args()
      
          migrator = TurborepoMigrator(
              path=args.path,
              dry_run=args.dry_run,
              package_manager=args.package_manager,
          )
      
          migrator.migrate()
      
      
      if __name__ == "__main__":
          main()
      
    • __init__.py 0 B
  • SKILL.md 9.2 KB
    ---
    name: web-frameworks
    description: Build modern full-stack web applications with Next.js (App Router, Server Components, RSC, PPR, SSR, SSG, ISR), Turborepo (monorepo management, task pipelines, remote caching, parallel execution), and RemixIcon (3100+ SVG icons in outlined/filled styles). Use when creating React applications, implementing server-side rendering, setting up monorepos with multiple packages, optimizing build performance and caching strategies, adding icon libraries, managing shared dependencies, or working with TypeScript full-stack projects.
    license: MIT
    version: 1.0.0
    ---
    
    # Web Frameworks Skill Group
    
    Comprehensive guide for building modern full-stack web applications using Next.js, Turborepo, and RemixIcon.
    
    ## Overview
    
    This skill group combines three powerful tools for web development:
    
    **Next.js** - React framework with SSR, SSG, RSC, and optimization features
    **Turborepo** - High-performance monorepo build system for JavaScript/TypeScript
    **RemixIcon** - Icon library with 3,100+ outlined and filled style icons
    
    ## When to Use This Skill Group
    
    - Building new full-stack web applications with modern React
    - Setting up monorepos with multiple apps and shared packages
    - Implementing server-side rendering and static generation
    - Optimizing build performance with intelligent caching
    - Creating consistent UI with professional iconography
    - Managing workspace dependencies across multiple projects
    - Deploying production-ready applications with proper optimization
    
    ## Stack Selection Guide
    
    ### Single Application: Next.js + RemixIcon
    
    Use when building a standalone application:
    - E-commerce sites
    - Marketing websites
    - SaaS applications
    - Documentation sites
    - Blogs and content platforms
    
    **Setup:**
    ```bash
    npx create-next-app@latest my-app
    cd my-app
    npm install remixicon
    ```
    
    ### Monorepo: Next.js + Turborepo + RemixIcon
    
    Use when building multiple applications with shared code:
    - Microfrontends
    - Multi-tenant platforms
    - Internal tools with shared component library
    - Multiple apps (web, admin, mobile-web) sharing logic
    - Design system with documentation site
    
    **Setup:**
    ```bash
    npx create-turbo@latest my-monorepo
    # Then configure Next.js apps in apps/ directory
    # Install remixicon in shared UI packages
    ```
    
    ### Framework Features Comparison
    
    | Feature | Next.js | Turborepo | RemixIcon |
    |---------|---------|-----------|-----------|
    | Primary Use | Web framework | Build system | UI icons |
    | Best For | SSR/SSG apps | Monorepos | Consistent iconography |
    | Performance | Built-in optimization | Caching & parallel tasks | Lightweight fonts/SVG |
    | TypeScript | Full support | Full support | Type definitions available |
    
    ## Quick Start
    
    ### Next.js Application
    
    ```bash
    # Create new project
    npx create-next-app@latest my-app
    cd my-app
    
    # Install RemixIcon
    npm install remixicon
    
    # Import in layout
    # app/layout.tsx
    import 'remixicon/fonts/remixicon.css'
    
    # Start development
    npm run dev
    ```
    
    ### Turborepo Monorepo
    
    ```bash
    # Create monorepo
    npx create-turbo@latest my-monorepo
    cd my-monorepo
    
    # Structure:
    # apps/web/          - Next.js application
    # apps/docs/         - Documentation site
    # packages/ui/       - Shared components with RemixIcon
    # packages/config/   - Shared configs
    # turbo.json         - Pipeline configuration
    
    # Run all apps
    npm run dev
    
    # Build all packages
    npm run build
    ```
    
    ### RemixIcon Integration
    
    ```tsx
    // Webfont (HTML/CSS)
    <i className="ri-home-line"></i>
    <i className="ri-search-fill ri-2x"></i>
    
    // React component
    import { RiHomeLine, RiSearchFill } from "@remixicon/react"
    <RiHomeLine size={24} />
    <RiSearchFill size={32} color="blue" />
    ```
    
    ## Reference Navigation
    
    **Next.js References:**
    - [App Router Architecture](./references/nextjs-app-router.md) - Routing, layouts, pages, parallel routes
    - [Server Components](./references/nextjs-server-components.md) - RSC patterns, client vs server, streaming
    - [Data Fetching](./references/nextjs-data-fetching.md) - fetch API, caching, revalidation, loading states
    - [Optimization](./references/nextjs-optimization.md) - Images, fonts, scripts, bundle analysis, PPR
    
    **Turborepo References:**
    - [Setup & Configuration](./references/turborepo-setup.md) - Installation, workspace config, package structure
    - [Task Pipelines](./references/turborepo-pipelines.md) - Dependencies, parallel execution, task ordering
    - [Caching Strategies](./references/turborepo-caching.md) - Local cache, remote cache, cache invalidation
    
    **RemixIcon References:**
    - [Integration Guide](./references/remix-icon-integration.md) - Installation, usage, customization, accessibility
    
    ## Common Patterns & Workflows
    
    ### Pattern 1: Full-Stack Monorepo
    
    ```
    my-monorepo/
    ├── apps/
    │   ├── web/              # Customer-facing Next.js app
    │   ├── admin/            # Admin dashboard Next.js app
    │   └── docs/             # Documentation site
    ├── packages/
    │   ├── ui/               # Shared UI with RemixIcon
    │   ├── api-client/       # API client library
    │   ├── config/           # ESLint, TypeScript configs
    │   └── types/            # Shared TypeScript types
    └── turbo.json            # Build pipeline
    ```
    
    **turbo.json:**
    ```json
    {
      "$schema": "https://turbo.build/schema.json",
      "pipeline": {
        "build": {
          "dependsOn": ["^build"],
          "outputs": [".next/**", "!.next/cache/**", "dist/**"]
        },
        "dev": {
          "cache": false,
          "persistent": true
        },
        "lint": {},
        "test": {
          "dependsOn": ["build"]
        }
      }
    }
    ```
    
    ### Pattern 2: Shared Component Library
    
    ```tsx
    // packages/ui/src/button.tsx
    import { RiLoader4Line } from "@remixicon/react"
    
    export function Button({ children, loading, icon }) {
      return (
        <button>
          {loading ? <RiLoader4Line className="animate-spin" /> : icon}
          {children}
        </button>
      )
    }
    
    // apps/web/app/page.tsx
    import { Button } from "@repo/ui/button"
    import { RiHomeLine } from "@remixicon/react"
    
    export default function Page() {
      return <Button icon={<RiHomeLine />}>Home</Button>
    }
    ```
    
    ### Pattern 3: Optimized Data Fetching
    
    ```tsx
    // app/posts/[slug]/page.tsx
    import { notFound } from 'next/navigation'
    
    // Static generation at build time
    export async function generateStaticParams() {
      const posts = await getPosts()
      return posts.map(post => ({ slug: post.slug }))
    }
    
    // Revalidate every hour
    async function getPost(slug: string) {
      const res = await fetch(`https://api.example.com/posts/${slug}`, {
        next: { revalidate: 3600 }
      })
      if (!res.ok) return null
      return res.json()
    }
    
    export default async function Post({ params }: { params: { slug: string } }) {
      const post = await getPost(params.slug)
      if (!post) notFound()
    
      return <article>{post.content}</article>
    }
    ```
    
    ### Pattern 4: Monorepo CI/CD Pipeline
    
    ```yaml
    # .github/workflows/ci.yml
    name: CI
    on: [push, pull_request]
    
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with:
              node-version: 18
          - run: npm install
          - run: npx turbo run build test lint
            env:
              TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
              TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
    ```
    
    ## Utility Scripts
    
    Python utilities in `scripts/` directory:
    
    **nextjs-init.py** - Initialize Next.js project with best practices
    **turborepo-migrate.py** - Convert existing monorepo to Turborepo
    
    Usage examples:
    ```bash
    # Initialize new Next.js app with TypeScript and recommended setup
    python scripts/nextjs-init.py --name my-app --typescript --app-router
    
    # Migrate existing monorepo to Turborepo with dry-run
    python scripts/turborepo-migrate.py --path ./my-monorepo --dry-run
    
    # Run tests
    cd scripts/tests
    pytest
    ```
    
    ## Best Practices
    
    **Next.js:**
    - Default to Server Components, use Client Components only when needed
    - Implement proper loading and error states
    - Use Image component for automatic optimization
    - Set proper metadata for SEO
    - Leverage caching strategies (force-cache, revalidate, no-store)
    
    **Turborepo:**
    - Structure monorepo with clear separation (apps/, packages/)
    - Define task dependencies correctly (^build for topological)
    - Configure outputs for proper caching
    - Enable remote caching for team collaboration
    - Use filters to run tasks on changed packages only
    
    **RemixIcon:**
    - Use line style for minimal interfaces, fill for emphasis
    - Maintain 24x24 grid alignment for crisp rendering
    - Provide aria-labels for accessibility
    - Use currentColor for flexible theming
    - Prefer webfonts for multiple icons, SVG for single icons
    
    ## Resources
    
    - Next.js: https://nextjs.org/docs/llms.txt
    - Turborepo: https://turbo.build/repo/docs
    - RemixIcon: https://remixicon.com
    
    ## Implementation Checklist
    
    Building with this stack:
    
    - [ ] Create project structure (single app or monorepo)
    - [ ] Configure TypeScript and ESLint
    - [ ] Set up Next.js with App Router
    - [ ] Configure Turborepo pipeline (if monorepo)
    - [ ] Install and configure RemixIcon
    - [ ] Implement routing and layouts
    - [ ] Add loading and error states
    - [ ] Configure image and font optimization
    - [ ] Set up data fetching patterns
    - [ ] Configure caching strategies
    - [ ] Add API routes as needed
    - [ ] Implement shared component library (if monorepo)
    - [ ] Configure remote caching (if monorepo)
    - [ ] Set up CI/CD pipeline
    - [ ] Configure deployment platform
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related