src/app
directory.src/components
directory.src/components/ui
.
cn
utility function for conditional class names.global.css
/ tailwind.config.ts
for styling.sonner
for notifications.<Image>
component for optimized image loading.tailwind.config.ts
."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}
/>
);
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.src/server/api/routers
.createTRPCRouter
, publicProcedure
, or protectedProcedure
from src/server/api/trpc.ts
.trpc
caller for server-side calls.trpc
React hooks like useQuery
and useMutation
for client-side calls."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
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>;
trpc
router for input validation.prisma/schema.prisma
.db
instance from src/server/db.ts
for database queries.db
instance to the client.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 },
});
}
db
instance directly.import { db } from "@/server/db";
export async function createRule(data: CreateRuleSchema) {
return db.rule.create({ data });
}
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>;
trpc
router for input validation.export const createRuleRouter = createTRPCRouter({
create: publicProcedure
.input(createRuleSchema)
.mutation(async ({ input }) => {
return createRule(input);
}),
});
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();
useSession()
to get the user session.const session = useSession();
.env
file (not committed to version control).process.env
in server-side code.