一键导入
nextjs
Next.js 16 App Router patterns for FTC Metrics. Use when creating pages, layouts, components, API routes, or implementing authentication flows.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Next.js 16 App Router patterns for FTC Metrics. Use when creating pages, layouts, components, API routes, or implementing authentication flows.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | nextjs |
| description | Next.js 16 App Router patterns for FTC Metrics. Use when creating pages, layouts, components, API routes, or implementing authentication flows. |
| license | MIT |
| compatibility | ["Claude Code"] |
| metadata | {"author":"ftcmetrics","version":"1.0.0","category":"frontend"} |
Patterns for the FTC Metrics Next.js frontend at packages/web/.
cd packages/web
bun dev # http://localhost:3000
bun typecheck # Type check
bun lint # Lint
packages/web/src/
app/ # App Router pages and layouts
layout.tsx # Root layout with Providers
page.tsx # Landing page (Server Component)
dashboard/layout.tsx # Auth-protected layout
analytics/page.tsx # Client Component with Suspense
analytics/team/[teamNumber]/ # Dynamic route
api/auth/[...nextauth]/ # Auth API routes
components/
providers.tsx # Client-side providers wrapper
header.tsx # Navigation header
lib/
auth.ts # NextAuth v5 configuration
api.ts # API client utilities
types/next-auth.d.ts # Session type augmentation
| Concept | Pattern | Location |
|---|---|---|
| Server Components | Default, no directive | app/page.tsx |
| Client Components | "use client" directive | components/header.tsx |
| Protected Routes | auth() check in layout | app/dashboard/layout.tsx |
| Dynamic Routes | [param] folder naming | app/analytics/team/[teamNumber]/ |
| API Routes | Route handlers | app/api/auth/[...nextauth]/route.ts |
// app/layout.tsx
import { Providers } from "@/components/providers";
import "./globals.css";
export const metadata = {
title: "FTC Metrics",
description: "Scouting platform for FIRST Tech Challenge",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className="min-h-screen bg-background antialiased">
<Providers>{children}</Providers>
</body>
</html>
);
}
// components/providers.tsx
"use client";
import { SessionProvider } from "next-auth/react";
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}
// app/dashboard/layout.tsx
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
import { Header } from "@/components/header";
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
const session = await auth();
if (!session?.user) redirect("/login");
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
<Header />
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">{children}</main>
</div>
);
}
// app/dashboard/page.tsx
import { auth } from "@/lib/auth";
export default async function DashboardPage() {
const session = await auth();
return <h1>Welcome, {session?.user?.name?.split(" ")[0] || "Scout"}</h1>;
}
// app/analytics/page.tsx
"use client";
import { Suspense } from "react";
import { useSearchParams, useRouter } from "next/navigation";
function AnalyticsContent() {
const searchParams = useSearchParams();
const router = useRouter();
const eventCode = searchParams.get("event") || "";
const handleEventChange = (event: string) => {
router.push(`/analytics?event=${event}`, { scroll: false });
};
return <div>{/* Content */}</div>;
}
export default function AnalyticsPage() {
return (
<Suspense fallback={<LoadingSpinner />}>
<AnalyticsContent />
</Suspense>
);
}
// app/analytics/team/[teamNumber]/page.tsx
"use client";
import { useParams, useSearchParams } from "next/navigation";
import { Suspense } from "react";
function TeamContent() {
const params = useParams();
const searchParams = useSearchParams();
const teamNumber = parseInt(params.teamNumber as string, 10);
const eventCode = searchParams.get("event") || "";
return <h1>Team {teamNumber}</h1>;
}
export default function TeamPage() {
return (
<Suspense fallback={<LoadingSpinner />}>
<TeamContent />
</Suspense>
);
}
// lib/auth.ts
import NextAuth from "next-auth";
import { PrismaAdapter } from "@auth/prisma-adapter";
import Google from "next-auth/providers/google";
import { prisma } from "@ftcmetrics/db";
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
Google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
pages: { signIn: "/login", error: "/login" },
callbacks: {
async session({ session, user }) {
if (session.user) session.user.id = user.id;
return session;
},
},
session: { strategy: "database" },
});
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/lib/auth";
export const { GET, POST } = handlers;
// types/next-auth.d.ts
import { DefaultSession } from "next-auth";
declare module "next-auth" {
interface Session {
user: { id: string } & DefaultSession["user"];
}
}
// components/header.tsx
"use client";
import { useSession, signOut } from "next-auth/react";
import Link from "next/link";
export function Header() {
const { data: session } = useSession();
return (
<header>
{session?.user ? (
<button onClick={() => signOut({ callbackUrl: "/" })}>Sign out</button>
) : (
<Link href="/login">Sign in</Link>
)}
</header>
);
}
// app/page.tsx
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
export default async function Home() {
const session = await auth();
if (session?.user) redirect("/dashboard");
return <LandingPage />;
}
// Server Component - uses auth()
export default async function Page() {
const session = await auth();
return <Display user={session?.user} />;
}
// Client Component - uses useSession hook
"use client";
import { useSession } from "next-auth/react";
export function UserProfile() {
const { data: session, status } = useSession();
if (status === "loading") return <Skeleton />;
return <Profile user={session?.user} />;
}
// WRONG: Will cause hydration errors
"use client";
export default function SearchPage() {
const params = useSearchParams(); // No Suspense wrapper
return <Results query={params.get("q")} />;
}
// CORRECT: Wrap in Suspense
"use client";
function SearchContent() {
const params = useSearchParams();
return <Results query={params.get("q")} />;
}
export default function SearchPage() {
return (
<Suspense fallback={<Loading />}>
<SearchContent />
</Suspense>
);
}
// Server actions - import from lib/auth
import { signIn, signOut } from "@/lib/auth";
// Client components - import from next-auth/react
"use client";
import { signIn, signOut } from "next-auth/react";
// tailwind.config.ts
const config = {
content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
theme: {
extend: {
colors: {
ftc: {
orange: "#f57e25", // Primary: text-ftc-orange, bg-ftc-orange
blue: "#0066b3", // Secondary: text-ftc-blue, bg-ftc-blue
dark: "#1a1a2e", // Dark: bg-ftc-dark
},
},
},
},
};
// next.config.ts
const nextConfig = {
reactStrictMode: true,
transpilePackages: ["@ftcmetrics/shared"], // Monorepo packages
};
// Workspace packages
import { prisma } from "@ftcmetrics/db";
import { TeamType } from "@ftcmetrics/shared";
// Local imports with @/ alias
import { auth } from "@/lib/auth";
import { Header } from "@/components/header";
Used throughout the codebase:
<div className="flex items-center justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-2 border-ftc-orange border-t-transparent" />
</div>
Understand and work with EPA and OPR analytics algorithms in FTC Metrics. Use when calculating team ratings, implementing match predictions, troubleshooting analytics calculations, or understanding the scoring methodology.
Manage Bun monorepo workspaces and dependencies in the FTC Metrics project. Use when adding dependencies, running package scripts, troubleshooting workspace resolution, or managing cross-package imports.
Integrate with the official FIRST Tech Challenge Events API in FTC Metrics. Use when fetching events, teams, matches, scores, rankings, or troubleshooting FTC API authentication and responses.
Build type-safe REST APIs with Hono framework in FTC Metrics. Use when creating API routes, implementing middleware, handling authentication, configuring CORS, or working with packages/api.
Configure NextAuth.js v5 authentication with OAuth providers and Prisma adapter. Use when setting up login/logout, protecting routes, accessing sessions in components, adding OAuth providers, or troubleshooting authentication issues.
Configure and use Prisma 7 ORM with PostgreSQL driver adapters in the FTC Metrics project. Use when setting up database schemas, running migrations, troubleshooting Prisma configuration, or working with the packages/db package.