Skip to content

Next.js

apps/web/
├── app/ # App Router
│ ├── (auth)/ # Auth routes (sign-in, sign-up)
│ ├── (protected)/ # Authenticated routes
│ ├── api/ # API routes
│ └── layout.tsx # Root layout
├── components/ # React components
├── lib/ # Utilities
└── next.config.ts # Next.js config

Route groups (name) organize routes without affecting URLs:

app/
├── (auth)/
│ ├── sign-in/page.tsx # /sign-in
│ └── sign-up/page.tsx # /sign-up
├── (protected)/
│ └── dashboard/page.tsx # /dashboard
└── page.tsx # /
app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
// app/(protected)/layout.tsx
export default function ProtectedLayout({ children }: { children: React.ReactNode }) {
return (
<AuthGuard>
<Sidebar />
<main>{children}</main>
</AuthGuard>
)
}

Use "use client" for interactive components:

"use client"
import { useState } from "react"
export function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount((c) => c + 1)}>{count}</button>
}

Default behavior - can fetch data directly:

// app/page.tsx (Server Component by default)
export default async function Page() {
const data = await fetchData()
return <div>{data}</div>
}
app/api/hello/route.ts
import { NextResponse } from "next/server"
export async function GET() {
return NextResponse.json({ message: "Hello" })
}
export async function POST(request: Request) {
const body = await request.json()
return NextResponse.json({ received: body })
}
import { Button } from "@repo/ui/components/ui/button"
import { Card } from "@repo/ui/components/ui/card"
// Or barrel import
import { Button, Card, Input } from "@repo/ui/components/ui"
import { cn } from "@repo/ui/lib/utils"
import { api } from "@repo/backend/convex"
import { useMutation, useQuery } from "convex/react"

In .env.local:

Terminal window
NEXT_PUBLIC_CONVEX_URL=https://...
CONVEX_DEPLOYMENT=dev:...

Access in code:

// Client-side (must have NEXT_PUBLIC_ prefix)
const url = process.env.NEXT_PUBLIC_CONVEX_URL
// Server-side only
const secret = process.env.API_SECRET
Terminal window
# Development
turbo dev --filter=web
# Build
turbo build --filter=web
# Type check
turbo check-types --filter=web