<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	
	xmlns:georss="http://www.georss.org/georss"
	xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
	>

<channel>
	<title>next.js &#8211; Blog of Code</title>
	<atom:link href="https://www.cztcode.com/category/web-build/next-js-web-build/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.cztcode.com</link>
	<description></description>
	<lastBuildDate>Mon, 27 Nov 2023 07:03:10 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.cztcode.com/wp-content/uploads/2024/02/cropped-logo-32x32.webp</url>
	<title>next.js &#8211; Blog of Code</title>
	<link>https://www.cztcode.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">217219486</site>	<item>
		<title>Next.js项目初始化配置</title>
		<link>https://www.cztcode.com/2023/5073/</link>
					<comments>https://www.cztcode.com/2023/5073/#respond</comments>
		
		<dc:creator><![CDATA[Jellow]]></dc:creator>
		<pubDate>Tue, 17 Oct 2023 09:31:48 +0000</pubDate>
				<category><![CDATA[next.js]]></category>
		<guid isPermaLink="false">https://www.cztcode.com/?p=5073</guid>

					<description><![CDATA[记录使用next.js(app router)+tailwind+typestript+prisma+trpc+tanstack+shadcn/ui项目初始化结构]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>记录下next.js全栈项目初始化需要的组件</p>
<ol>
<li>next.js (app router)</li>
<li>tailwind</li>
<li>typescript</li>
<li>prisma</li>
<li>trpc</li>
<li>tanstack</li>
<li>shadcn/ui</li>
</ol>
<h2>使用pnpm初始化next.js</h2>
<pre><code class="language-bash">pnpm dlx create-next-app@latest</code></pre>
<p>起好项目名字后全部默认就可以</p>
<pre><code class="language-bash">(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</code></pre>
<h2>使用eslint和prettier</h2>
<p>一、安装eslint + prettier + eslint-plugin-prettier</p>
<pre><code class="language-bash">pnpm i eslint prettier eslint-plugin-prettier eslint-config-prettier prettier-plugin-tailwindcss -D</code></pre>
<p>二、配置.eslintrc.json</p>
<pre><code class="language-tsx">{
  &quot;extends&quot;: [
    &quot;next/core-web-vitals&quot;,
    &quot;eslint:recommended&quot;,
    &quot;plugin:prettier/recommended&quot;
  ],
  // 这个是nextjs官网推荐的写法
  &quot;env&quot;: {
    &quot;es2020&quot;:true,
    &quot;es6&quot;: true,
    &quot;node&quot;: true
  },
  &quot;plugins&quot;: [
    &quot;prettier&quot;
  ],
  &quot;rules&quot;: {
    &quot;prettier/prettier&quot;: &quot;warn&quot;
  }
}</code></pre>
<p>三、新建.prettierrc.js，注意名字别打错了</p>
<pre><code class="language-tsx">module.exports = {
  // 基础配置
  singleQuote: false, // 使用双引号
  printWidth: 100, // 设置行宽度为100，稍微宽一点可能更适合现代屏幕
  semi: true, // 使用分号
  trailingComma: &#039;all&#039;, // 在多行输入的尾逗号处添加一个逗号

  // TypeScript相关配置
  arrowParens: &#039;always&#039;, // 箭头函数参数始终使用括号，有助于代码清晰
  bracketSpacing: true, // 在对象字面量声明所使用的的大括号内部添加空格

  // 与编辑器和其他工具集成
  useTabs: false, // 使用空格替代Tab
  tabWidth: 2, // 设置tab宽度为2

  // 其他配置
  proseWrap: &#039;always&#039;, // 总是折行，适用于Markdown等
  htmlWhitespaceSensitivity: &#039;css&#039;, // 尊重CSS中的空白字符
  plugins: [&#039;prettier-plugin-tailwindcss&#039;],
};
</code></pre>
<p>手动格式化</p>
<pre><code class="language-bash"> prettier --write &quot;src/**/*.{js,jsx,ts,tsx}&quot;    </code></pre>
<p>添加到package.json</p>
<pre><code class="language-bash"> &quot;prettier&quot;: &quot;prettier --write \&quot;src/**/*.{js,jsx,ts,tsx}\&quot;&quot;</code></pre>
<p>推荐开启保存自动格式化</p>
<p><img decoding="async" src="https://markdown.cztcode.com/ccc2ffb497138a58eca75f94fc5c6b3a.png" alt="image-20231017165849028" /></p>
<h2>安装husky</h2>
<p>husky负责实现在git提交前检查规范</p>
<pre><code class="language-bash">pnpm dlx husky-init &amp;&amp; pnpm install</code></pre>
<p>运行husky初始化，上面的脚本会帮我们添加一个prepare指令</p>
<pre><code class="language-bash"> pnpm run prepare   </code></pre>
<h3>修改配置</h3>
<p>把.husky/pre-commit 修改为</p>
<pre><code class="language-bash">pnpm run prettier
pnpm run lint</code></pre>
<p>记得把.husky添加到git</p>
<h2>安装commitlint</h2>
<p>commitlint可以让提交信息更规范</p>
<pre><code class="language-bash"> pnpm install  -D @commitlint/{config-conventional,cli}       </code></pre>
<p>初始化</p>
<pre><code class="language-bash">echo &quot;module.exports = {extends: [&#039;@commitlint/config-conventional&#039;]}&quot; &gt; commitlint.config.js</code></pre>
<p>把commitlint添加进husky</p>
<pre><code class="language-bash"> pnpm dlx husky add .husky/commit-msg  &#039;npx --no -- commitlint --edit ${1}&#039;</code></pre>
<p>记得把commitlint.config.js添加到git</p>
<h2>安装prisma</h2>
<p>prisma 是nextjs中热门的ORM组件</p>
<pre><code class="language-bash">pnpm dlx prisma  </code></pre>
<p>初始化prisma</p>
<pre><code class="language-bash"> pnpm dlx prisma init  </code></pre>
<p>在.env文件中更改数据库链接字符串DATABASE_URL，小项目建议去Mongodb Atlas</p>
<p>模型定义在prisma/schema.prisma，这里我给出mongodb的配置和一些简单类型</p>
<pre><code class="language-tsx">generator client {
  provider = &quot;prisma-client-js&quot;
  output   = &quot;./client&quot; // 如果写了这个就不用配置webstorm排除目录了
}

datasource db {
  provider     = &quot;mongodb&quot;
  url          = env(&quot;DATABASE_URL&quot;)
  relationMode = &quot;prisma&quot;
}

model Post {
  id       String    @id @default(auto()) @map(&quot;_id&quot;) @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(&quot;_id&quot;) @db.ObjectId
  email   String   @unique
  name    String?
  address Address?
  posts   Post[]
}

model Comment {
  id      String @id @default(auto()) @map(&quot;_id&quot;) @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
}
</code></pre>
<p>prisma会生成client便于类型提示，在你更改完schema.prisma后要运行</p>
<pre><code class="language-bash">pnpm dlx prisma generate</code></pre>
<p>在修改表后，还要运行push指令同步到数据库</p>
<pre><code class="language-bash">pnpm dlx prisma db push</code></pre>
<p>使用studio指令查看数据库表</p>
<pre><code class="language-bash">pnpm dlx prisma studio  </code></pre>
<p>如果你使用的是webstorm，注意要把这个文件夹标记为不排除，因为node_module这里webstorm默认不会生成索引，当你使用<code>prisma generate</code>后会动态生成新的代码，而由于webstorm默认不生成索引，你就不会得到ts的代码提示。</p>
<p>排除了就会重新索引这个文件，具体路径</p>
<pre><code class="language-bash">/node_modules/.pnpm/@prisma+client@5.4.2/node_modules/.prisma</code></pre>
<p><img decoding="async" src="https://markdown.cztcode.com/d64f5bd5742d6b9422c01f78cd7482f2.png" alt="image-20231017155450168" /></p>
<p>创建一个prisma客户端实例化代码，这段代码会在开发环境复用client实例（避免next的热更新造成重复创建实例）。</p>
<pre><code class="language-tsx">import { PrismaClient } from &quot;@prisma/client&quot;;

const prismaClientSingleton = () =&gt; {
  return new PrismaClient();
};

type PrismaClientSingleton = ReturnType&lt;typeof prismaClientSingleton&gt;;

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClientSingleton | undefined;
};

const prisma = globalForPrisma.prisma ?? prismaClientSingleton();

export default prisma;

if (process.env.NODE_ENV !== &quot;production&quot;) globalForPrisma.prisma = prisma;</code></pre>
<h2>配置shadcn/ui</h2>
<p>shadcn是一个开源的，可定制化很高的组件库，与传统的导入整个组件库不同，shadcn/ui会精确导入你使用的组件，并且你可以直接进一步修改这些组件，就像是自己写的一样。</p>
<p><a href="https://ui.shadcn.com/" target="_blank" rel="noopener">https://ui.shadcn.com/</a></p>
<pre><code class="language-bash">pnpm dlx shadcn-ui@latest init</code></pre>
<p>除了tailwind.config.js需要改成ts，其他都用默认</p>
<pre><code class="language-bash">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
</code></pre>
<p>需要任何组件从这里找https://ui.shadcn.com/docs/components/</p>
<p>文档里有写每个组件如何安装</p>
<pre><code class="language-bash">pnpx dlx shadcn-ui@latest add xxx</code></pre>
<p>之后正常在page中使用即可</p>
<h2>使用trpc</h2>
<pre><code class="language-bash">pnpm add @trpc/server @trpc/client  @trpc/react-query   </code></pre>
<p>创建trpc文件夹，然后分别创建client.ts，init.ts，index.ts</p>
<p>client.ts</p>
<pre><code class="language-tsx">import { AppRouter } from &quot;@/trpc&quot;;
import { createTRPCReact } from &quot;@trpc/react-query&quot;;

export const trpc = createTRPCReact&lt;AppRouter&gt;({});
</code></pre>
<p>index.ts 这里放端点，我还使用了zod动态验证数据格式，你可以安装pnpm install zod</p>
<p>下面写了一个创建新用户的例子</p>
<pre><code class="language-ts">import db from &quot;@/lib/prisma&quot;;
import { z } from &quot;zod&quot;;
import { publicProcedure, router } from &quot;@/trpc/init&quot;;

export const appRouter = router({
  createUser: publicProcedure
    .input(
      z.object({
        name: z.string(),
        email: z.string(),
      }),
    )
    .query(async ({ input }) =&gt; {
      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;
</code></pre>
<p>init.ts 在这里你可以定义公开procedure，或者利用middleware创建私有的</p>
<pre><code class="language-ts">import { initTRPC } from &quot;@trpc/server&quot;;

/**
 * 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;
</code></pre>
<p>给一个使用kinde的例子</p>
<pre><code class="language-ts">import { getKindeServerSession } from &#039;@kinde-oss/kinde-auth-nextjs/server&#039;
import { TRPCError, initTRPC } from &#039;@trpc/server&#039;

const t = initTRPC.create()
const middleware = t.middleware

const isAuth = middleware(async (opts) =&gt; {
  const { getUser } = getKindeServerSession()
  const user = getUser()

  if (!user || !user.id) {
    throw new TRPCError({ code: &#039;UNAUTHORIZED&#039; })
  }

  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)
</code></pre>
<p>推荐使用tanstack帮助trpc进行请求状态管理</p>
<p><a href="https://tanstack.com/query/latest/docs/react/overview" target="_blank" rel="noopener">https://tanstack.com/query/latest/docs/react/overview</a></p>
<p>安装tanstack</p>
<pre><code class="language-bash">pnpm add @tanstack/react-query</code></pre>
<p>具体使用方法推荐查看官网</p>
<p>现在需要创建一个Provider，把trpc与tanstack结合，在conponents文件夹下</p>
<pre><code class="language-ts">&quot;use client&quot;;

import { PropsWithChildren, useState } from &quot;react&quot;;
import { QueryClient, QueryClientProvider } from &quot;@tanstack/react-query&quot;;
import { httpBatchLink } from &quot;@trpc/client&quot;;
import { trpc } from &quot;@/trpc/client&quot;;

const Providers = ({ children }: PropsWithChildren) =&gt; {
  const [queryClient] = useState(() =&gt; new QueryClient());
  const [trpcClient] = useState(() =&gt;
    trpc.createClient({
      links: [
        httpBatchLink({
          url: &quot;http://localhost:3000/api/trpc&quot;,
        }),
      ],
    }),
  );
  return (
    &lt;trpc.Provider queryClient={queryClient} client={trpcClient}&gt;
      &lt;QueryClientProvider client={queryClient}&gt;{children}&lt;/QueryClientProvider&gt;
    &lt;/trpc.Provider&gt;
  );
};

export default Providers;</code></pre>
<p>最后在最父层layout.tsx中把里面用Provider，你就可以在任何地方使用了</p>
<pre><code class="language-tsx">import type { Metadata } from &quot;next&quot;;
import { Inter } from &quot;next/font/google&quot;;
import &quot;./globals.css&quot;;
import React from &quot;react&quot;;
import Providers from &quot;@/components/Providers&quot;;

const inter = Inter({ subsets: [&quot;latin&quot;] });

export const metadata: Metadata = {
  title: &quot;Create Next App&quot;,
  description: &quot;Generated by create next app&quot;,
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    &lt;html lang=&quot;en&quot;&gt;
      &lt;Providers&gt;
        &lt;body className={inter.className}&gt;{children}&lt;/body&gt;
      &lt;/Providers&gt;
    &lt;/html&gt;
  );
}
</code></pre>
<p>一个项目的初始化就完成啦</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cztcode.com/2023/5073/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5073</post-id>	</item>
	</channel>
</rss>
