.cursorrules
Login

Cursor Rules for Next.js | ShadCN | tRPC | Prisma | Turso

1

Cursor Rules for Next.js | ShadCN | tRPC | Prisma | Turso

Project Structure and Setup

TypeScript Usage

  • Use TypeScript for all new files and components.

Package Manager

  • Use pnpm as the main package manager.

Next.js Structure

  • Follow the Next.js 14+ app directory structure:
    • Place pages in the src/app directory.
    • Use layout files for shared layouts.
    • Place components in the src/components directory.

UI Components and Styling

UI Components

  • Use the shadcn/ui component library for UI components, located in src/components/ui.
    • Use the cn utility function for conditional class names.
    • Use theme colors from global.css / tailwind.config.ts for styling.
    • Use toast from sonner for notifications.
    • Use the Next.js <Image> component for optimized image loading.

Tailwind CSS

  • Use Tailwind CSS for styling.
    • Configure Tailwind in tailwind.config.ts.

Custom Data Table

  • For data tables, use the custom DataTable component with the following structure:
"use client";
const columnsConfig: TableColumnConfig<Data>[] = [
  {
    accessorKey: "name",
    header: "Name",
    cell: (data) => <div>{data.name}</div>,
  },
];

const {
  data: rules,
  isLoading: isLoadingData,
  isFetching: isFetchingData,
} = api.rules.getAll.useQuery();

const isLoading = isLoadingData || isFetchingData;

const columns = createColumns(columnsConfig);

return (
  <DataTable
    tableKey="rules-table"
    data={rules}
    columns={columns}
    placeholder="Search rules"
    isLoadingData={isLoading}
  />
);

Server-Side Rendering and API

Server-Side Rendering (SSR)

  • Implement server-side rendering (SSR) where possible, using Next.js features.
  • For the server-side only files like src/server/db.ts, src/server/auth.ts and src/server/api/trpc.ts use import "server-only" to avoid hydration errors and leaking sensitive information to the client.

tRPC for API Routes

  • Use tRPC for type-safe API routes:
    • Define routers in src/server/api/routers.
    • Use createTRPCRouter, publicProcedure, or protectedProcedure from src/server/api/trpc.ts.
    • Use trpc caller for server-side calls.
    • Use trpc React hooks like useQuery and useMutation for client-side calls.

Example

"use client";
import { api } from "@/trpc/react";
const {
  data: rules,
  isLoading: isLoadingData,
  isFetching: isFetchingData,
} = api.rules.getAll.useQuery();

const isLoading = isLoadingData || isFetchingData;

const { mutateAsync: createRuleAsync, isPending: isCreatingRule } =
  api.rules.create.useMutation({
    onSuccess: (result) => {
      // do something with the result if needed
    },
    onError: (error) => {
      // do something with the error if needed
    },
  });

For server-side calls:

import { api } from "@/trpc/server";
const rules = await api.rules.getAll(); // no need for useQuery hook, it's a server-side call

Input Validation and Form Handling

Input Validation with Zod

  • Use zod for input validation in trpc.
import { z } from "zod";

export const createRuleSchema = z.object({
  title: z.string(),
  content: z.string(),
  isPrivate: z.boolean(),
  tags: z.array(z.string()),
});

export type CreateRuleSchema = z.infer<typeof createRuleSchema>;
  • Export schemas and use them in the trpc router for input validation.

Form Handling

  • Implement form handling using react-hook-form and zod for validation.

Database Operations

Prisma for Database Operations

  • Use Prisma for database operations.
    • Define models in prisma/schema.prisma.
    • Use the db instance from src/server/db.ts for database queries.
    • Never expose the db instance to the client.
    • Use sqlite database from Turso with libsql.

Queries and Mutations

  • Organize queries in the queries folder and schemas in the schemas folder. Use them in trpc routers for input validation.
import { db } from "@/server/db";

export async function getRules() {
  return db.rule.findMany({
    where: { isPrivate: false },
    include: { author: true, tags: true, votes: true },
  });
}
  • For mutations, use the db instance directly.
import { db } from "@/server/db";

export async function createRule(data: CreateRuleSchema) {
  return db.rule.create({ data });
}
  • For schemas, use the zod library under src/server/schemas.
import { z } from "zod";

export const createRuleSchema = z.object({
  title: z.string(),
  content: z.string(),
  isPrivate: z.boolean(),
  tags: z.array(z.string()),
});

export type CreateRuleSchema = z.infer<typeof createRuleSchema>;
  • and use them in the trpc router for input validation.
export const createRuleRouter = createTRPCRouter({
  create: publicProcedure
    .input(createRuleSchema)
    .mutation(async ({ input }) => {
      return createRule(input);
    }),
});

Authentication and Security

Authentication with NextAuth.js

  • Use NextAuth.js for authentication.

    • Define authentication options in src/server/auth.ts.

    • Use auth() to get the user session on the server side.

    • Use useSession() to get the user session on the client side.

    • Use server-side authentication as much as possible.

    • For server components, use auth() to get the user session.

    const session = await auth();
    
    • For client components, use useSession() to get the user session.
    const session = useSession();
    

Environment Variables

  • Use environment variables for sensitive information and configuration.
    • Store them in the .env file (not committed to version control).
    • Access them using process.env in server-side code.

Error Handling and Asynchronous Operations

Error Handling and Loading States

  • Implement error handling and loading states for asynchronous operations.

Code Style and Naming Conventions

Naming Conventions

  • Follow these conventions:
    • Use PascalCase for component names.
    • Use camelCase for function and variable names.
    • Use UPPERCASE_SNAKE_CASE for constants.
Created by ariburaco on 9/13/2024
nextjs
t3stack
trpc
prisma
turso
tailwind
typescript