Skip to Content

Next.js 14 + NextAuth V5 用户验证系统

落地页

落地页

调整页面默认高度

在进行接下来的内容之前,让我们先来修改一个next.js框架的默认设置。

为了了解前后差别,我们先给app目录下面的page.tsx中的外层div添加一个h-full bg-black的样式:

app/page.tsx
import { Button } from "@/components/ui/button"; export default function Home() { return ( <div className="flex space-x-2 m-4 h-full bg-slate-200"> <Button variant="default">default</Button> <Button variant="destructive">destructive</Button> <Button variant="outline">outline</Button> <Button variant="secondary">secondary</Button> <Button variant="ghost">ghost</Button> <Button variant="link">link</Button> <Button variant="theme">Theme</Button> </div> ); }

然后来看下效果:

5-1-bg-before.png

可以看到,尽管我们给最外层div设置了h-full高度占满屏幕,它还是只作用到了div里面内容的高度。

现在我们在app下面的globals.css中增加一段代码,就可以让高度撑满整个屏幕了:

app/globals.css
@tailwind base; @tailwind components; @tailwind utilities; html, body, :root { height: 100%; } body { font-family: Arial, Helvetica, sans-serif; } @layer utilities { .text-balance { text-wrap: balance; } } /* 原来的其他代码... */

这时候再看首页的效果,就是这样了:

5-2-bg-after.png

创建一个落地页

现在,让我们将app目录下的page.tsx文件删掉,然后创建一个(marketing)目录,用来放我们的落地页相关文件。由于我们这个项目是以用户鉴权为主的,所以我们就简单让AI来帮我们生成一个落地页,再稍微改一下内容即可。

这个落地页主要包含navbar、hero、features、stats四个组件,我们先在(marketing)目录下创建_components目录,用于存放与落地页相关的组件,然后在_components目录下分别创建四个组件:

导航栏Navbar组件

app/(marketing)/_components/navbar.tsx
import { Button } from "@/components/ui/button"; import Link from "next/link"; export function Navbar() { return ( <header className="border-b"> <div className="container mx-auto px-4"> <div className="flex h-16 items-center justify-between"> <div className="flex gap-6 items-center"> <Link href="/" className="font-bold text-xl"> DevDocLib </Link> </div> <div className="flex items-center gap-4"> <Button variant="ghost" asChild> <Link href="/sign-in">登录</Link> </Button> <Button asChild> <Link href="/sign-up">注册</Link> </Button> </div> </div> </div> </header> ); }

头部Hero组件

app/(marketing)/_components/hero.tsx
import { Button } from "@/components/ui/button"; import { ArrowRight } from "lucide-react"; import Link from "next/link"; export const Hero = () => { return ( <section className="flex flex-col items-center justify-center px-4 py-20 text-center space-y-8"> <h1 className="text-6xl font-bold tracking-tight"> 完整用户鉴权功能<br /> </h1> <h1 className="text-6xl font-bold tracking-tight"> 快速启动你的Next.js项目 </h1> <p className="text-xl text-muted-foreground max-w-2xl"> 使用最新的技术栈快速开发安全、可扩展的应用程序,包含完整的用户认证和数据库集成 </p> <div className="flex gap-4"> <Button variant="theme" size="lg" asChild> <Link href="/sign-up"> 立即开始 <ArrowRight className="ml-2 h-4 w-4" /> </Link> </Button> </div> </section> ) }

特性features组件

app/(marketing)/_components/features.tsx
import { CheckCircle } from "lucide-react"; const features = [ { title: "Next.js 14 & TypeScript", description: "使用最新的Next.js 14和TypeScript,确保类型安全和最佳性能", icon: CheckCircle, }, { title: "Auth.js认证系统", description: "集成Auth.js实现完整的用户认证流程,支持多种登录方式", icon: CheckCircle, }, { title: "Shadcn UI组件库", description: "使用shadcn/ui和Tailwind CSS构建美观且响应式的界面", icon: CheckCircle, }, { title: "Prisma ORM数据库集成", description: "通过Prisma ORM实现类型安全的数据库操作", icon: CheckCircle, }, { title: "Server action", description: "使用Next.js server action实现用户验证功能", icon: CheckCircle, }, { title: "阿里云OSS图像存储", description: "使用阿里云OSS实现用户头像存储变更功能", icon: CheckCircle, }, ]; export const Features = () => { return ( <section className="py-20 bg-slate-50"> <div className="container mx-auto px-4"> <h2 className="text-3xl font-bold text-center mb-12">技术特性</h2> <div className="grid md:grid-cols-3 gap-8"> {features.map((feature, index) => ( <div key={index} className="p-6 bg-white rounded-lg shadow-sm"> <feature.icon className="h-12 w-12 text-primary mb-4" /> <h3 className="text-xl font-semibold mb-2">{feature.title}</h3> <p className="text-muted-foreground">{feature.description}</p> </div> ))} </div> </div> </section> ); }

统计stats组件

app/(marketing)/_components/stats.tsx
export const Stats = () => { return ( <section className="py-20"> <div className="container mx-auto px-4"> <div className="grid md:grid-cols-3 gap-8 text-center"> <div> <div className="text-4xl font-bold text-primary">100%</div> <p className="text-muted-foreground">类型安全</p> </div> <div> <div className="text-4xl font-bold text-primary">现代化</div> <p className="text-muted-foreground">技术栈</p> </div> <div> <div className="text-4xl font-bold text-primary">快速</div> <p className="text-muted-foreground">开发体验</p> </div> </div> </div> </section> ); }

创建MarketingLayout

实践中,一般会把navbar组件放在layout中,其他部分则放在page中作为main标签里面的内容,所以我们需要在(marketing)目录下创建一个layout.tsx

app/(marketing)/layout.tsx
import { Navbar } from "./_components/navbar"; export default function MarketingLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <> <Navbar /> <main> {children} </main> </> ); }

在Marketing page中引入各个组件

然后在page.tsx中引入我们前面创建的Hero、Features和Stats组件:

app/(marketing)/page.tsx
import { Hero } from "./_components/hero"; import { Features } from "./_components/features"; import { Stats } from "./_components/stats"; export default function Home() { return ( <div className="flex flex-col min-h-screen"> <Hero /> <Features /> <Stats /> </div> ); }

项目目录结构

现在,我们项目的app目录是这样的:

    • favicon.ico
    • globals.css
    • layout.tsx

这时候访问首页 http://localhost:3000 ,看到的应该是这个效果:

5-3-landing-page.png

设置项目主题色

颜色稍微有点素,其实我们仔细看ai给我们生成的代码,里面有一些text-primary之类的样式是没有生效的,这是一些主题设置。那如何给我们的网站设置主题色呢?

我们可以去shadcn的官网,找到theme,然后选择一个颜色,复制对应的代码到app目录下的globals.css中即可。这里我们选择这个蓝色:

5-4-shadcn-theme.png

然后点击Copy code,复制这些代码:

5-5-shadcn-theme-code.png

然后找到app目录下的globals.css文件,替换掉里面原来的@layer base设置:

app/globals.css
@tailwind base; @tailwind components; @tailwind utilities; html, body, :root { height: 100%; } body { font-family: Arial, Helvetica, sans-serif; } @layer utilities { .text-balance { text-wrap: balance; } } @layer base { :root { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; --popover: 0 0% 100%; --popover-foreground: 222.2 84% 4.9%; --primary: 221.2 83.2% 53.3%; --primary-foreground: 210 40% 98%; --secondary: 210 40% 96.1%; --secondary-foreground: 222.2 47.4% 11.2%; --muted: 210 40% 96.1%; --muted-foreground: 215.4 16.3% 46.9%; --accent: 210 40% 96.1%; --accent-foreground: 222.2 47.4% 11.2%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; --ring: 221.2 83.2% 53.3%; --radius: 0.75rem; --chart-1: 12 76% 61%; --chart-2: 173 58% 39%; --chart-3: 197 37% 24%; --chart-4: 43 74% 66%; --chart-5: 27 87% 67%; } .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; --card: 222.2 84% 4.9%; --card-foreground: 210 40% 98%; --popover: 222.2 84% 4.9%; --popover-foreground: 210 40% 98%; --primary: 217.2 91.2% 59.8%; --primary-foreground: 222.2 47.4% 11.2%; --secondary: 217.2 32.6% 17.5%; --secondary-foreground: 210 40% 98%; --muted: 217.2 32.6% 17.5%; --muted-foreground: 215 20.2% 65.1%; --accent: 217.2 32.6% 17.5%; --accent-foreground: 210 40% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: 224.3 76.3% 48%; --chart-1: 220 70% 50%; --chart-2: 160 60% 45%; --chart-3: 30 80% 55%; --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; } } @layer base { * { @apply border-border; } body { @apply bg-background text-foreground; } }

现在,落地页就变成了这样:

5-6-landing-page-with-theme.png

小标题和一些图标的颜色就变成我们设置的蓝色主题色了。但是还存在一个问题,就是我们最开始自定义的theme button颜色跟主题色不一致了,不用担心,我们再看一下button组件中的default样式代码使用的就是primary主题色,所以我们现在就可以把自定义的theme样式删掉了:

components/ui/button.tsx
variants: { variant: { default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", // theme: "bg-indigo-500 text-white hover:bg-indigo-700" 删掉这一行 },

然后同样删掉navbar和hero两个组件中设置的theme variant即可。

Navbar组件:

app/(marketing)/_components/navbar.tsx
import { Button } from "@/components/ui/button"; import Link from "next/link"; export function Navbar() { return ( <header className="border-b"> <div className="container mx-auto px-4"> <div className="flex h-16 items-center justify-between"> <div className="flex gap-6 items-center"> <Link href="/" className="font-bold text-xl"> DevDocLib </Link> </div> <div className="flex items-center gap-4"> <Button variant="ghost" asChild> <Link href="/sign-in">登录</Link> </Button> <Button asChild> <Link href="/sign-up">注册</Link> </Button> </div> </div> </div> </header> ); }

Hero组件:

app/(marketing)/_components/hero.tsx
import { Button } from "@/components/ui/button"; import { ArrowRight } from "lucide-react"; import Link from "next/link"; export const Hero = () => { return ( <section className="flex flex-col items-center justify-center px-4 py-20 text-center space-y-8"> <h1 className="text-6xl font-bold tracking-tight"> 完整用户鉴权功能<br /> </h1> <h1 className="text-6xl font-bold tracking-tight"> 快速启动你的Next.js项目 </h1> <p className="text-xl text-muted-foreground max-w-2xl"> 使用最新的技术栈快速开发安全、可扩展的应用程序,包含完整的用户认证和数据库集成 </p> <div className="flex gap-4"> <Button size="lg" asChild> <Link href="/sign-up"> 立即开始 <ArrowRight className="ml-2 h-4 w-4" /> </Link> </Button> </div> </section> ) }

现在,整体颜色就统一了:

5-7-landing-page-theme-blue.png

添加背景渐变色

由于白色背景显得有些空,我们再在layout中中的main标签中添加一个浅色的渐变背景:

app/(marketing)/layout.tsx
import { Navbar } from "./_components/navbar"; export default function MarketingLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <> <Navbar /> <main className="pt-20 bg-gradient-to-r from-blue-100/50 via-indigo-100/50 to-violet-50/50"> {children} </main> </> ); }

再优化下navbar的样式,增加一个半透明和固定到顶部的效果:

app/(marketing)/_components/navbar.tsx
import { Button } from "@/components/ui/button"; import Link from "next/link"; export function Navbar() { return ( <header className="fixed top-0 w-full h-20 px-4 md:px-8 py-2.5 bg-white/75 backdrop-blur-sm border-b border-gray-200/20 flex items-center z-50"> <div className="container mx-auto px-4"> <div className="flex h-16 items-center justify-between"> <div className="flex gap-6 items-center"> <Link href="/" className="font-bold text-xl"> DevDocLib </Link> </div> <div className="flex items-center gap-4"> <Button variant="ghost" asChild> <Link href="/sign-in">登录</Link> </Button> <Button asChild> <Link href="/sign-up">注册</Link> </Button> </div> </div> </div> </header> ); }

这时候再看下首页,就好看多了:

5-8-landing-page-new.png

好了,落地页到此为止。接下来,我们就要开始来实现项目的注册登录功能了。细心的朋友应该发现了,在我们的落地页上,有两个注册按钮,一个是navbar组件中的“注册”按钮,还有一个是hero组件中的“立即开始”按钮,这两个按钮都已经被添加了跳转到注册页(sign-up page)的链接。接下来,就让我们先来实现一下注册页面。