记录下next.js全栈项目初始化需要的组件
- next.js (app router)
- tailwind
- typescript
- prisma
- trpc
- tanstack
- shadcn/ui
使用pnpm初始化next.js
pnpm dlx create-next-app@latest
起好项目名字后全部默认就可以
(base) ➜ ~ pnpm dlx create-next-app@latest
Packages: +1
+
Progress: resolved 1, reused 0, downloaded 1, added 1, done
✔ What is your project named? … demo
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
使用eslint和prettier
一、安装eslint + prettier + eslint-plugin-prettier
pnpm i eslint prettier eslint-plugin-prettier eslint-config-prettier prettier-plugin-tailwindcss -D
二、配置.eslintrc.json
{
"extends": [
"next/core-web-vitals",
"eslint:recommended",
"plugin:prettier/recommended"
],
// 这个是nextjs官网推荐的写法
"env": {
"es2020":true,
"es6": true,
"node": true
},
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": "warn"
}
}
三、新建.prettierrc.js,注意名字别打错了
module.exports = {
// 基础配置
singleQuote: false, // 使用双引号
printWidth: 100, // 设置行宽度为100,稍微宽一点可能更适合现代屏幕
semi: true, // 使用分号
trailingComma: 'all', // 在多行输入的尾逗号处添加一个逗号
// TypeScript相关配置
arrowParens: 'always', // 箭头函数参数始终使用括号,有助于代码清晰
bracketSpacing: true, // 在对象字面量声明所使用的的大括号内部添加空格
// 与编辑器和其他工具集成
useTabs: false, // 使用空格替代Tab
tabWidth: 2, // 设置tab宽度为2
// 其他配置
proseWrap: 'always', // 总是折行,适用于Markdown等
htmlWhitespaceSensitivity: 'css', // 尊重CSS中的空白字符
plugins: ['prettier-plugin-tailwindcss'],
};
手动格式化
prettier --write "src/**/*.{js,jsx,ts,tsx}"
添加到package.json
"prettier": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\""
推荐开启保存自动格式化
安装husky
husky负责实现在git提交前检查规范
pnpm dlx husky-init && pnpm install
运行husky初始化,上面的脚本会帮我们添加一个prepare指令
pnpm run prepare
修改配置
把.husky/pre-commit 修改为
pnpm run prettier
pnpm run lint
记得把.husky添加到git
安装commitlint
commitlint可以让提交信息更规范
pnpm install -D @commitlint/{config-conventional,cli}
初始化
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
把commitlint添加进husky
pnpm dlx husky add .husky/commit-msg 'npx --no -- commitlint --edit '
记得把commitlint.config.js添加到git
安装prisma
prisma 是nextjs中热门的ORM组件
pnpm dlx prisma
初始化prisma
pnpm dlx prisma init
在.env文件中更改数据库链接字符串DATABASE_URL,小项目建议去Mongodb Atlas
模型定义在prisma/schema.prisma,这里我给出mongodb的配置和一些简单类型
generator client {
provider = "prisma-client-js"
output = "./client" // 如果写了这个就不用配置webstorm排除目录了
}
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
relationMode = "prisma"
}
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
slug String @unique
title String
body String
author User @relation(fields: [authorId], references: [id])
authorId String @db.ObjectId
comments Comment[]
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String @unique
name String?
address Address?
posts Post[]
}
model Comment {
id String @id @default(auto()) @map("_id") @db.ObjectId
comment String
post Post @relation(fields: [postId], references: [id])
postId String @db.ObjectId
}
// Address is an embedded document
type Address {
street String
city String
state String
zip String
}
prisma会生成client便于类型提示,在你更改完schema.prisma后要运行
pnpm dlx prisma generate
在修改表后,还要运行push指令同步到数据库
pnpm dlx prisma db push
使用studio指令查看数据库表
pnpm dlx prisma studio
如果你使用的是webstorm,注意要把这个文件夹标记为不排除,因为node_module这里webstorm默认不会生成索引,当你使用prisma generate
后会动态生成新的代码,而由于webstorm默认不生成索引,你就不会得到ts的代码提示。
排除了就会重新索引这个文件,具体路径
/node_modules/.pnpm/@[email protected]/node_modules/.prisma
创建一个prisma客户端实例化代码,这段代码会在开发环境复用client实例(避免next的热更新造成重复创建实例)。
import { PrismaClient } from "@prisma/client";
const prismaClientSingleton = () => {
return new PrismaClient();
};
type PrismaClientSingleton = ReturnType<typeof prismaClientSingleton>;
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClientSingleton | undefined;
};
const prisma = globalForPrisma.prisma ?? prismaClientSingleton();
export default prisma;
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
配置shadcn/ui
shadcn是一个开源的,可定制化很高的组件库,与传统的导入整个组件库不同,shadcn/ui会精确导入你使用的组件,并且你可以直接进一步修改这些组件,就像是自己写的一样。
pnpm dlx shadcn-ui@latest init
除了tailwind.config.js需要改成ts,其他都用默认
Progress: resolved 198, reused 198, downloaded 0, added 198, done
✔ Would you like to use TypeScript (recommended)? … no / yes
✔ Which style would you like to use? › Default
✔ Which color would you like to use as base color? › Zinc
✔ Where is your global CSS file? … app/globals.css
✔ Would you like to use CSS variables for colors? … no / yes
✔ Where is your tailwind.config.js located? … tailwind.config.ts
✔ Configure the import alias for components: … @/components
✔ Configure the import alias for utils: … @/lib/utils
✔ Are you using React Server Components? … no / yes
✔ Write configuration to components.json. Proceed? … yes
需要任何组件从这里找https://ui.shadcn.com/docs/components/
文档里有写每个组件如何安装
pnpx dlx shadcn-ui@latest add xxx
之后正常在page中使用即可
使用trpc
pnpm add @trpc/server @trpc/client @trpc/react-query
创建trpc文件夹,然后分别创建client.ts,init.ts,index.ts
client.ts
import { AppRouter } from "@/trpc";
import { createTRPCReact } from "@trpc/react-query";
export const trpc = createTRPCReact<AppRouter>({});
index.ts 这里放端点,我还使用了zod动态验证数据格式,你可以安装pnpm install zod
下面写了一个创建新用户的例子
import db from "@/lib/prisma";
import { z } from "zod";
import { publicProcedure, router } from "@/trpc/init";
export const appRouter = router({
createUser: publicProcedure
.input(
z.object({
name: z.string(),
email: z.string(),
}),
)
.query(async ({ input }) => {
await db.user.create({
data: {
name: input.name,
email: input.email,
},
});
return { success: true };
}),
});
// Export type router type signature,
// NOT the router itself.
export type AppRouter = typeof appRouter;
init.ts 在这里你可以定义公开procedure,或者利用middleware创建私有的
import { initTRPC } from "@trpc/server";
/**
* Initialization of tRPC backend
* Should be done only once per backend!
*/
const t = initTRPC.create();
/**
* Export reusable router and procedure helpers
* that can be used throughout the router
*/
export const router = t.router;
export const publicProcedure = t.procedure;
给一个使用kinde的例子
import { getKindeServerSession } from '@kinde-oss/kinde-auth-nextjs/server'
import { TRPCError, initTRPC } from '@trpc/server'
const t = initTRPC.create()
const middleware = t.middleware
const isAuth = middleware(async (opts) => {
const { getUser } = getKindeServerSession()
const user = getUser()
if (!user || !user.id) {
throw new TRPCError({ code: 'UNAUTHORIZED' })
}
return opts.next({
ctx: {
userId: user.id,
user,
},
})
})
export const router = t.router
export const publicProcedure = t.procedure
export const privateProcedure = t.procedure.use(isAuth)
推荐使用tanstack帮助trpc进行请求状态管理
https://tanstack.com/query/latest/docs/react/overview
安装tanstack
pnpm add @tanstack/react-query
具体使用方法推荐查看官网
现在需要创建一个Provider,把trpc与tanstack结合,在conponents文件夹下
"use client";
import { PropsWithChildren, useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { httpBatchLink } from "@trpc/client";
import { trpc } from "@/trpc/client";
const Providers = ({ children }: PropsWithChildren) => {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: "http://localhost:3000/api/trpc",
}),
],
}),
);
return (
<trpc.Provider queryClient={queryClient} client={trpcClient}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</trpc.Provider>
);
};
export default Providers;
最后在最父层layout.tsx中把里面用Provider,你就可以在任何地方使用了
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import React from "react";
import Providers from "@/components/Providers";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<Providers>
<body className={inter.className}>{children}</body>
</Providers>
</html>
);
}
一个项目的初始化就完成啦