共计 6952 个字符,预计需要花费 18 分钟才能阅读完成。
背景
随着 ChatGPT
的炽热,国外很多开发者疾速响应,利用于不同场景的 AI 利用井喷式的暴发,并且根本集中在 web
畛域利用,而在疾速开发的背地,咱们能够看到,开发者大多抉择 Next.js
或者 Nuxt.js
全栈框架来开发,以疾速验证本人的产品。这种选型的背地,我感觉次要起因有:
-
SEO
的重要性国外更加重视 SEO 的重要性,国内搜索引擎大多是靠花钱买搜寻流量,包含小程序、App 这类对 SEO 的需要并不大
-
Edge Function
的衰亡Serverless 使得前端开发能疾速开发全栈利用,不便的托管本人后端服务,而不必过多关注部署,然而他的毛病是,少数 Serverless 都是采纳容器化的计划,因而冷启动工夫长,如果在本人的云函数转发申请 OpenAI 接口,可能会产生申请工夫很长的状况。现在,Vercel、CloudFlare、Supabase 等厂商都有了 Edge Function 的能力,使得函数能够在一些间隔用户更近的边缘节点运行,为了更快的冷启动工夫来疾速响应用户,这种计划个别也加了局部限度,然而仍旧失去很多开发者的青眼
-
云服务厂商的多样性
云服务厂商提供了很多根底服务,当把我的项目托管给 Vercel 等服务时,能够与 Github 集成进行继续部署,同时还会调配一个域名。很多其余厂商也提供了很多的收费后端存储服务,例如:
- Upsatsh 提供的 Redis
- Supabase 提供的 PostgreSQL
- PlantScale 提供的 MySQL
- Clerk 提供的用户认证和用户治理
以上这些厂商的收费打算对于集体开发齐全够用,当然也能够依据产品规模应用付费打算
而本文旨在尝试开发一个简略的导航页面,满足本人的收集嗜好,用于解放本人的收藏夹,来学习 Next.js
开发,体验 Next.js
带来的开发全栈利用的便捷性。
初始化我的项目
随着以 tailwindcss
、unocss
这种原子化 CSS
计划的呈现,基于此衍生进去的 UI 组件库也很多,比方 Radix、daisyUI、flowbite,其中的 RadixUI 组件库相当重视网页的可拜访性,组件都遵循 WAI-ARIA 规范,这使得开发者构建可拜访的 UI 界面变得更加容易,而因为他专一于可拜访性、属于 Headless UI
也就是无具体款式类名代码,因而 shadcn 作者开发了 shadcn/ui 组件库,在此 RadixUI
组件库根底上赋予了简洁好看的款式,失去了很多开发者的青眼,也十分举荐大家体验下。
这里间接抉择克隆该作者的 Nextjs 模版初始化我的项目
git clone https://github.com/shadcn/next-template
该我的项目应用了 Next.js
最新的 app router
版本,并且曾经集成了 tailwindcss
和shadcn/ui
组件库。这里抉择做导航网站也是因为它足够简略,要害款式是针对侧边栏,因为 tailwindcss
是挪动端优先,所以这里设置默认暗藏,当屏幕宽度大于 sm
时展现。
<div className="fixed z-20 hidden min-h-screen sm:block">
...
</div>
而对于列表区域,采纳 grid
布局,默认挪动端优先一列,依据屏幕大小展现 2
列或者 3
列
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 md:gap-6 lg:grid-cols-3">
...
</div>
其余的款式能够借鉴一下他人的网站设计简略丑化下,对于不太熟悉 css
并且不足审美的我花了不少的工夫在调整,总感觉网站不够好看,然而又不晓得该如何丑化。
数据库集成
定义模型
数据库集成这里我抉择了 Prisma
,相似的比拟热门的还有Drizzle
,针对Prisma
的具体概念、应用和它的长处,能够参考我整顿记录的笔记。执行
npx prisma init
会创立 prisma/schema.prisma
文件,创立模型如下
generator client {provider = "prisma-client-js"}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model Category {id String @id @default(cuid())
icon String
title String
description String
rank Int?
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @default(now()) @map(name: "updated_at")
links Link[]
@@map(name: "category")
}
model Link {id String @id @default(cuid())
icon String
url String
title String
description String
rank Int?
public Boolean @default(true)
status Int @default(1) @db.TinyInt
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @default(now()) @map(name: "updated_at")
cid String
catagory Category @relation(fields: [cid], references: [id])
@@map(name: "link")
}
其中 DATABASE_URL
为近程数据库地址,这里我应用的是 PlantScale
的MySQL
。当然你也能够应用 Supabase
的PostgreSQL
或者其余数据库,创立 .env
文件,填入服务地址
DATABASE_URL='mysql://user:password@aws.connect.psdb.cloud/dev?sslaccept=strict'
能够在这个可视化 Prisma 模型网站看到关系图如下,别离示意类别实例和网站链接实例
表同步
而后执行命令将本地模型同步到数据库表
npx prisma db push
而后可能会遇到上面报错
查阅后发现官网在文档中有阐明,PlanetScale 的 MySQL
数据库不反对外键,须要非凡的指定relationMode
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
relationMode = "prisma"
}
relationMode
默认值是 foreignKeys
,当应用PlanetScale
数据库的 MySQL
连接器时,应启用此选项,在 Prisma Client
中模仿关系。再次执行 db push 指令,将模型同步到数据库
插入和查问数据
而后执行
npx prisma studio
关上表编辑器增加本人的数据
执行命令生成 PrismaClient 实例
pnpm install @prisma/client
npx prisma generate
而后就能够通过关联关系一次性查问出数据
import prisma from '@/lib/db';
import type {Prisma} from '@prisma/client';
export default async function getNavLinks() {
const res = await prisma.category.findMany({
orderBy: [
{rank: 'asc',}
],
include: {
links: {
orderBy: {rank: 'asc',},
where: {
public: true,
status: 1,
},
},
},
});
return res;
}
export type CategoryWithLinks = Prisma.PromiseReturnType<typeof getNavLinks>
用户认证
在 Next.js
中集成用户认证非常简单,能够间接应用NextAuth
NextAuth
NextAuth
是一个为 Next.js
应用程序提供的开源身份验证解决方案。默认状况下,NextAuth
应用 JSON Web Tokens(JWT)
保留用户会话。NextAuth 反对风行的登录服务,如 Google
、Facebook
、Auth0
、Apple
、电子邮件以及OAuth 1.0
和2.0
服务等等,是一种灵便可配置的身份验证解决方案。
首先装置所需依赖
pnpm install next-auth @next-auth/prisma-adapter
依照官网文档指引增加用户相干模型
model Account {id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
GitHub
获取 Github
客户端ID
,参考文档,蕴含以下步骤:
-
关上
GitHub
应用程序页面,点击创立Github
利用。 -
输出名称、主页、以及受权后的回调地址,这里开发环境时填
localhost:3000
,上线时切换成本人的线上域名即可。 -
点击”生成”并保留
Client ID
和Client Secret
Google
参考 NextAuth 文档指引,在 Google 开发者网站注册利用并抉择 api
服务
利用类型抉择 Web
利用,相似 Github
填上可信赖的域名和回调地址确认
创立 API 路由
首先在 .env
文件中增加下面保留的认证相干密钥,其中 NEXTAUTH_SECRET
是用户生成 JWT
的密钥
# google 登录
GOOGLE_CLIENT_ID="GOOGLE_CLIENT_ID"
GOOGLE_CLIENT_SECRET="GOOGLE_CLIENT_SECRET"
# github 登录
GITHUB_CLIENT_ID="GITHUB_CLIENT_ID"
GITHUB_CLIENT_SECRET="GITHUB_CLIENT_SECRET"
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="webnav"
能够看到下面的 API
回调地址别离是/api/auth/github
和 /api/auth/google
,创立app/api/auth/[…nextauth]/route.ts
文件,并增加以下代码片段:
import NextAuth, {type NextAuthOptions} from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import GitHubProvider from "next-auth/providers/github";
import CredentialsProvider from "next-auth/providers/credentials";
import {PrismaAdapter} from "@next-auth/prisma-adapter";
import {PrismaClient} from "@prisma/client";
import {compare} from "bcrypt";
const prisma = new PrismaClient();
export const authOptions: NextAuthOptions = {adapter: PrismaAdapter(prisma),
providers: [
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
CredentialsProvider({
name: "Credentials",
credentials: {email: { label: "Email", type: "email"},
password: {label: "Password", type: "password"},
},
async authorize(credentials) {const { email, password} = credentials ?? {}
if (!email || !password) {throw new Error("Missing username or password");
}
const user = await prisma.user.findUnique({
where: {email: credentials?.email,},
});
// if user doesn't exist or password doesn't match
if (!user || !(await compare(password, user.password!))) {throw new Error("Invalid username or password");
}
return user;
},
})
],
session: {strategy: "jwt",},
debug: process.env.NODE_ENV !== "production",
};
const handler = NextAuth(authOptions);
export {handler as GET, handler as POST};
其中,GitHubProvider
用户 Github
登录,GoogleProvider
用于 Google
登录,CredentialsProvider
用于自定义登录,这里会查看邮箱明码,匹配上了就返回用户信息。
同时还须要创立注册登录页面,创立 app/login/page.tsx
文件和 app/register/page.tsx
文件,页面间接复制的taxonomy 页面款式,本人加上google
登录按钮,成果如图
页面上通过
import {signIn, signOut} from "next-auth/react"
signIn
办法容许应用 Google
或GitHub
帐户进行登录。signOut
容许登记用户会话,具体应用能够参考官网文档这部分
部署
在 [Vercel](https://vercel.com/)
中间接导入我的项目,批改构建命令为
npx prisma generate && next build
在 build
之前学生成 PrismaClient
类型,不然编译时会因为类型报错而失败,同时增加 .env
中所须要的环境变量
同时因为咱们的数据源在数据库,而 Nextjs
默认是构建时生成页面的也就是 SSG
模式,在数据库有数据更新时咱们须要更新页面内容,因而咱们须要应用它的增量动态生成 ISR(Incremental Static Regeneration)
模式,参考官网文档,在 page.tsx
中增加导出,这里对更新时效性要求不高所以设置为 1
天
export const revalidate = 24 * 60 * 60;
从构建日志中能够看到曾经失效了
部署胜利后绑定自定义域名就完结撒花了。
总结
本文以简略的导航网站举例,联合Next.js
、Prisma
、NextAuth
、shadcn/ui
来学习如何构建全栈利用,你能够关上页面体验,也能够在本我的项目开源地址查看残缺代码,最初码字不易,如果你都看到这了,欢送 star 一波,感激。