Next.js项目初始化配置

记录下next.js全栈项目初始化需要的组件

  1. next.js (app router)
  2. tailwind
  3. typescript
  4. prisma
  5. trpc
  6. tanstack
  7. 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}\""

推荐开启保存自动格式化

image-20231017165849028

安装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

image-20231017155450168

创建一个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会精确导入你使用的组件,并且你可以直接进一步修改这些组件,就像是自己写的一样。

https://ui.shadcn.com/

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>
  );
}

一个项目的初始化就完成啦

发表评论