Next.js 16 Patterns
TinyKit Pro uses Next.js 16.0.10 with React 19. This guide documents the key patterns and best practices used in the codebase.
TinyKit Pro uses Next.js 16.0.10 with React 19. This guide documents the key patterns and best practices used in the codebase.
Promise-Based Params and SearchParams
Next.js 14+ changed the behavior of params and searchParams to be Promise-based. This affects all route handlers and page components.
Server Components (Pages)
// src/app/products/[productId]/page.tsx
export default async function ProductPage({
params,
searchParams
}: {
params: Promise<{ productId: string }>;
searchParams: Promise<{ tab?: string }>
}) {
// Await the params and searchParams
const { productId } = await params;
const { tab } = await searchParams;
return (
<div>
<h1>Product: {productId}</h1>
<p>Current tab: {tab ?? 'overview'}</p>
</div>
);
}Route Handlers
// src/app/api/items/[itemId]/route.ts
export async function GET(
request: Request,
{ params }: { params: Promise<{ itemId: string }> },
) {
const { itemId } = await params;
// Fetch item data...
return Response.json({ itemId });
}Client Components
For client components, continue using the useParams() hook from next/navigation:
"use client";
import { useParams } from "next/navigation";
export default function ClientPage() {
const params = useParams();
const orgSlug = params.orgSlug as string;
// Use params directly
return <div>Organization: {orgSlug}</div>;
}URL Query State with nuqs
TinyKit Pro uses nuqs for URL query state management instead of Next.js's useSearchParams. This provides type-safe, declarative query state.
Why nuqs?
- Type-safe URL state
- Automatic URL encoding/decoding
- Proper history management
- Cleaner URLs with
clearOnDefault
Basic Usage
"use client";
import { useQueryState, parseAsStringEnum, parseAsInteger } from "nuqs";
// Simple string filter
const [filter, setFilter] = useQueryState(
"filter",
parseAsStringEnum(["all", "active", "completed"])
.withDefault("all")
.withOptions({
history: "push",
clearOnDefault: true,
}),
);
// Usage
void setFilter("active"); // Updates URL to ?filter=active
void setFilter("all"); // Clears filter from URL (default)
// Pagination
const [page, setPage] = useQueryState(
"page",
parseAsInteger.withDefault(1).withOptions({
history: "replace",
clearOnDefault: true,
}),
);Reusable Query State Hooks
Create centralized hooks in src/lib/navigation/:
// src/lib/navigation/useQueryState.ts
import { useQueryState, parseAsStringEnum, parseAsInteger } from "nuqs";
export const useFilterState = () => {
return useQueryState(
"filter",
parseAsStringEnum(["all", "active", "completed"])
.withDefault("all")
.withOptions({ history: "push", clearOnDefault: true }),
);
};
export const usePaginationState = () => {
return useQueryState(
"page",
parseAsInteger.withDefault(1).withOptions({
history: "replace",
clearOnDefault: true,
}),
);
};Type-Safe Routing
TinyKit Pro uses a type-safe routing system with the defineRoute utility.
Defining Routes
// src/config/routes/types.ts
export type RouteSchemas = {
orgDashboard: {
params: { orgSlug: string };
};
orgMember: {
params: { orgSlug: string; memberId: string };
};
orgSettings: {
params: { orgSlug: string };
search?: { tab?: string };
};
};
// src/config/routes/org.ts
import { defineRoute } from "@/lib/routes";
export const orgRoutes = {
dashboard: defineRoute<RouteSchemas["orgDashboard"]>("/orgs/[orgSlug]"),
member: defineRoute<RouteSchemas["orgMember"]>(
"/orgs/[orgSlug]/members/[memberId]",
),
settings: defineRoute<RouteSchemas["orgSettings"]>(
"/orgs/[orgSlug]/settings",
),
};Using Type-Safe Routes
import { orgRoutes } from "@/config/routes";
import { useRouter } from "next/navigation";
function NavigationExample() {
const router = useRouter();
// Navigate with type-safe params
const handleNavigate = () => {
router.push(orgRoutes.dashboard({ orgSlug: "my-org" }));
// With search params
router.push(
orgRoutes.settings({
orgSlug: "my-org",
search: { tab: "members" },
}),
);
};
}Turbopack
Next.js 16 uses Turbopack as the default development bundler. No configuration needed - it's enabled automatically with next dev.
Benefits
- Faster cold starts
- Faster hot module replacement (HMR)
- Improved memory usage
- Better error messages
Compatibility
TinyKit Pro is fully compatible with Turbopack. If you encounter issues:
- Clear
.nextdirectory:rm -rf .next - Clear node_modules cache:
bun install --force - Check for unsupported webpack plugins (rare)
React 19 Features
TinyKit Pro uses React 19.2.3. Key features used:
Server Components
Most pages are server components by default. Use "use client" directive only when needed:
// Server Component (default) - can be async, access server resources
export default async function Page() {
const data = await fetchData(); // Direct server-side fetch
return <div>{data.content}</div>;
}
// Client Component - for interactivity
"use client";
export default function InteractivePage() {
const [state, setState] = useState(false);
return <button onClick={() => setState(!state)}>Toggle</button>;
}Suspense Boundaries
Use Suspense for loading states with server components:
import { Suspense } from "react";
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<StatsSkeleton />}>
<StatsSection />
</Suspense>
<Suspense fallback={<ChartSkeleton />}>
<ChartsSection />
</Suspense>
</div>
);
}Parallel Data Fetching
For server components, fetch data in parallel:
export default async function Page() {
// Parallel fetching - much faster than sequential
const [users, stats, settings] = await Promise.all([
fetchUsers(),
fetchStats(),
fetchSettings(),
]);
return (
<Dashboard users={users} stats={stats} settings={settings} />
);
}Route Groups
TinyKit Pro uses route groups for organization:
src/app/
├── (root)/ # Public landing pages (/, /waitlist)
├── auth/ # Authentication pages
├── home/ # User dashboard
├── admin/ # Admin interface
└── orgs/
├── (list)/ # Organization list page
└── (workspace)/ # Organization workspace pages
└── [orgSlug]/Route Group Conventions
(name)- Groups routes without affecting URL[param]- Dynamic route segment@slot- Parallel routes (slots)(.)folder- Intercepting routes
Common Patterns
Authentication Guard
import { Authenticated } from "convex/react";
export default function ProtectedPage() {
return (
<Authenticated>
<ProtectedContent />
</Authenticated>
);
}Loading and Error States
// loading.tsx - Automatic loading UI
export default function Loading() {
return <PageSkeleton />;
}
// error.tsx - Error boundary
"use client";
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
);
}Migration from Next.js 15
If migrating from Next.js 15:
- Update params typing: Add
Promise<>wrapper to all params/searchParams - Add await: Ensure you await params before accessing
- Check useSearchParams: Replace with nuqs for new code
- Test Turbopack: Verify all pages work with Turbopack
- Update dependencies: Ensure React 19 compatibility
Suspense & Server Components Architecture
TinyKit Pro implements strategic use of React Suspense boundaries and Server Components to optimize both SEO performance and user experience. This document o...
Modular Routing System
TinyKit Pro implements a centralized, modular route configuration system that separates routes by domain (core auth, public pages, admin panel, organizations...