├── app ├── favicon.ico ├── api │ └── auth │ │ └── [...nextauth] │ │ └── route.ts ├── (protected) │ └── dashboard │ │ └── page.tsx ├── lib │ └── actions.ts ├── layout.tsx ├── sign-in │ └── page.tsx ├── sign-up │ └── page.tsx ├── globals.css └── page.tsx ├── remove_mee.png ├── .env.example ├── next.config.mjs ├── config.ts ├── routes.ts ├── auth.ts ├── lib ├── prisma.ts ├── form-schemas.ts └── actions.ts ├── components └── SubmitButton.tsx ├── .gitignore ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── package.json ├── auth.config.ts ├── LICENSE.MD ├── prisma └── schema.prisma ├── middleware.ts └── README.md /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmvergara/nextjs-mongodb-prisma-auth-template/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /remove_mee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmvergara/nextjs-mongodb-prisma-auth-template/HEAD/remove_mee.png -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL="mongodb+srv://test:test@cluster0.ns1yp.mongodb.net/myFirstDatabase" 2 | AUTH_SECRET=MeowMeowMeow -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { handlers } from "@/auth"; // Referring to the auth.ts we just created 2 | export const { GET, POST } = handlers; 3 | -------------------------------------------------------------------------------- /config.ts: -------------------------------------------------------------------------------- 1 | if (!process.env.DATABASE_URL) throw new Error("DATABASE_URL must be set"); 2 | export const DATABASE_URL = process.env.DATABASE_URL; 3 | console.log("DATABASE_URL", DATABASE_URL); 4 | -------------------------------------------------------------------------------- /routes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Public routes 3 | * @type {string[]} 4 | */ 5 | export const publicRoutes = ["/", "/sign-in", "/sign-up"]; 6 | 7 | export const authRoutes = ["/sign-in", "/sign-up"]; 8 | 9 | export const apiAuthPrefix = "/api/auth"; 10 | -------------------------------------------------------------------------------- /auth.ts: -------------------------------------------------------------------------------- 1 | import { PrismaAdapter } from "@auth/prisma-adapter"; 2 | import prisma from "./lib/prisma"; 3 | import { Adapter } from "next-auth/adapters"; 4 | import NextAuth from "next-auth"; 5 | import { authConfig } from "./auth.config"; 6 | 7 | export const { handlers, auth, signIn, signOut } = NextAuth({ 8 | adapter: PrismaAdapter(prisma) as Adapter, 9 | session: { strategy: "jwt" }, 10 | ...authConfig, 11 | }); 12 | -------------------------------------------------------------------------------- /lib/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | const prismaClientSingleton = () => { 4 | return new PrismaClient(); 5 | }; 6 | 7 | type PrismaClientSingleton = ReturnType; 8 | 9 | // eslint-disable-next-line 10 | const globalForPrisma = globalThis as unknown as { 11 | prisma: PrismaClientSingleton | undefined; 12 | }; 13 | 14 | const prisma = globalForPrisma.prisma ?? prismaClientSingleton(); 15 | 16 | export default prisma; 17 | -------------------------------------------------------------------------------- /components/SubmitButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useFormStatus } from "react-dom"; 4 | import { type ComponentProps } from "react"; 5 | 6 | type Props = ComponentProps<"button"> & { 7 | pendingText: string; 8 | }; 9 | 10 | export function SubmitButton({ children, pendingText, ...props }: Props) { 11 | const { pending } = useFormStatus(); 12 | 13 | return ( 14 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /app/(protected)/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth } from "@/auth"; 2 | 3 | import Link from "next/link"; 4 | 5 | const DashboardPage = async () => { 6 | const session = await auth(); 7 | return ( 8 |
9 | 10 | ◄ Home 11 | 12 |
13 |

This is a Protected Page

14 |

Current User username : {session?.user?.email || "None"}

15 |
16 |
17 | ); 18 | }; 19 | 20 | export default DashboardPage; 21 | -------------------------------------------------------------------------------- /app/lib/actions.ts: -------------------------------------------------------------------------------- 1 | import { signIn } from "@/auth"; 2 | import { AuthError } from "next-auth"; 3 | 4 | // ... 5 | 6 | export async function authenticate( 7 | prevState: string | undefined, 8 | formData: FormData 9 | ) { 10 | try { 11 | await signIn("credentials", formData); 12 | } catch (error) { 13 | if (error instanceof AuthError) { 14 | switch (error.type) { 15 | case "CredentialsSignin": 16 | return "Invalid credentials."; 17 | default: 18 | return "Something went wrong."; 19 | } 20 | } 21 | throw error; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "NextJS MongoDB Prisma Starter", 9 | description: "NextJS MongoDB Prisma Starter with TypeScript and TailwindCSS", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-mongodb-prisma-auth-template", 3 | "version": "0.1.0", 4 | "private": true, 5 | "author": "mmvergara", 6 | "description": "Template repository for building a Next.js application with MongoDB, Prisma, and Next Auth authentication", 7 | "scripts": { 8 | "dev": "next dev", 9 | "build": "prisma generate && next build", 10 | "start": "next start", 11 | "lint": "next lint" 12 | }, 13 | "dependencies": { 14 | "@auth/prisma-adapter": "^2.0.0", 15 | "@prisma/client": "^5.13.0", 16 | "bcryptjs": "^2.4.3", 17 | "next": "^14.2.18", 18 | "next-auth": "^5.0.0-beta.17", 19 | "react": "^18", 20 | "react-dom": "^18", 21 | "zod": "^3.23.5" 22 | }, 23 | "devDependencies": { 24 | "@types/bcryptjs": "^2.4.6", 25 | "@types/node": "^20", 26 | "@types/react": "^18", 27 | "@types/react-dom": "^18", 28 | "prisma": "^5.13.0", 29 | "typescript": "^5" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/form-schemas.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | const { object, string } = z; 3 | 4 | export const signInSchema = object({ 5 | email: string({ required_error: "Email is required" }).email("Invalid email"), 6 | password: string({ required_error: "Password is required" }) 7 | .min(8, "Password must be more than 8 characters") 8 | .max(32, "Password must be less than 32 characters"), 9 | }); 10 | export type SignInValues = z.infer; 11 | 12 | export const signUpSchema = object({ 13 | email: string({ required_error: "Email is required" }).email("Invalid email"), 14 | password: string({ required_error: "Password is required" }) 15 | .min(8, "Password must be more than 8 characters") 16 | .max(32, "Password must be less than 32 characters"), 17 | username: string({ required_error: "Username is required" }) 18 | .min(4, "Username is required") 19 | .max(32, "Username must be less than 32 characters"), 20 | }); 21 | export type SignUpValues = z.infer; 22 | -------------------------------------------------------------------------------- /auth.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextAuthConfig } from "next-auth"; 2 | import { signInSchema } from "./lib/form-schemas"; 3 | import { compare } from "bcryptjs"; 4 | import Credentials from "next-auth/providers/credentials"; 5 | import prisma from "./lib/prisma"; 6 | 7 | export const authConfig = { 8 | providers: [ 9 | Credentials({ 10 | async authorize(credentials) { 11 | // Validate the fields 12 | const validatedFields = signInSchema.safeParse(credentials); 13 | if (!validatedFields.success) { 14 | return null; 15 | } 16 | 17 | // Validate that the user exists 18 | const { email, password } = validatedFields.data; 19 | const user = await prisma.user.findUnique({ 20 | where: { email }, 21 | }); 22 | if (!user) { 23 | return null; 24 | } 25 | 26 | // Check the password 27 | const isPasswordMatch = await compare(password, user.password); 28 | if (!isPasswordMatch) { 29 | return null; 30 | } 31 | 32 | return user; 33 | }, 34 | }), 35 | ], 36 | } satisfies NextAuthConfig; 37 | -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 MARK MATTHEW M. VERGARA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/sign-in/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { SubmitButton } from "@/components/SubmitButton"; 3 | import { signInAction } from "@/lib/actions"; 4 | import Link from "next/link"; 5 | import { redirect } from "next/navigation"; 6 | 7 | export default function SignInPage() { 8 | const handleFormSubmit = async (formData: FormData) => { 9 | const email = formData.get("email") as string; 10 | const password = formData.get("password") as string; 11 | const res = await signInAction({ email, password }); 12 | if (res.error) { 13 | alert(res.error); 14 | return; 15 | } 16 | redirect("/"); 17 | }; 18 | 19 | return ( 20 |
21 | 22 | ◄ Home 23 | 24 |
25 |

Sign In

26 | 27 | 28 | Login 29 | 30 | Don't have an account? Sign Up 31 | 32 |
33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | 2 | generator client { 3 | provider = "prisma-client-js" 4 | } 5 | 6 | datasource db { 7 | provider = "mongodb" 8 | url = env("DATABASE_URL") 9 | } 10 | 11 | 12 | 13 | model User { 14 | id String @id @default(auto()) @map("_id") @db.ObjectId 15 | username String 16 | email String @unique 17 | password String 18 | image String? 19 | accounts Account[] 20 | 21 | createdAt DateTime @default(now()) 22 | updatedAt DateTime @updatedAt 23 | } 24 | 25 | model Account { 26 | id String @id @default(auto()) @map("_id") @db.ObjectId 27 | userId String @db.ObjectId 28 | type String 29 | provider String 30 | providerAccountId String 31 | refresh_token String? @db.String 32 | access_token String? @db.String 33 | expires_at Int? 34 | token_type String? 35 | scope String? 36 | id_token String? @db.String 37 | session_state String? 38 | 39 | createdAt DateTime @default(now()) 40 | updatedAt DateTime @updatedAt 41 | 42 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 43 | 44 | @@unique([provider, providerAccountId]) 45 | } 46 | 47 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import { auth } from "./auth"; 3 | import { apiAuthPrefix, authRoutes, publicRoutes } from "./routes"; 4 | 5 | export default auth((req) => { 6 | const { nextUrl } = req; 7 | const isLoggedIn = !!req.auth; 8 | 9 | const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix); 10 | const isPublicRotue = publicRoutes.includes(nextUrl.pathname); 11 | const isAuthRoute = authRoutes.includes(nextUrl.pathname); 12 | 13 | // if it is an API Next Auth route, we don't want to redirect 14 | if (isApiAuthRoute) return; 15 | 16 | if (isAuthRoute) { 17 | if (isLoggedIn) { 18 | // if the user is already logged in and is in sign-in or sign-up page 19 | // redirect to the default logged in page (which is dashboard in this case) 20 | console.log("redirecting to dashboard"); 21 | return NextResponse.redirect(new URL("/dashboard", nextUrl)); 22 | } 23 | // if the user is not logged in and is in sign-in or sign-up page, let them be 24 | return; 25 | } 26 | 27 | if (!isLoggedIn && !isPublicRotue) { 28 | // if the user is not logged in and is not in a public route, redirect to sign-in page 29 | return NextResponse.redirect(new URL("/sign-in", nextUrl)); 30 | } 31 | 32 | return; 33 | }); 34 | 35 | export const config = { 36 | matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"], 37 | }; 38 | -------------------------------------------------------------------------------- /lib/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { SignInValues, SignUpValues, signUpSchema } from "./form-schemas"; 3 | import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library"; 4 | import { AuthError } from "next-auth"; 5 | import { hashSync } from "bcryptjs"; 6 | import { signIn } from "@/auth"; 7 | import prisma from "./prisma"; 8 | import { redirect } from "next/navigation"; 9 | 10 | export const signInAction = async (signInValues: SignInValues) => { 11 | try { 12 | await signIn("credentials", signInValues); 13 | } catch (error) { 14 | if (error instanceof AuthError) { 15 | switch (error.type) { 16 | case "CredentialsSignin": 17 | return { error: "Invalid Credentials" }; 18 | default: 19 | return { error: "An error occurred" }; 20 | } 21 | } 22 | throw error; 23 | } 24 | redirect("/dashboard"); 25 | }; 26 | 27 | export const signUpAction = async (signUpValues: SignUpValues) => { 28 | const { data } = await signUpSchema.safeParseAsync(signUpValues); 29 | if (!data) return { error: "Invalid data" }; 30 | try { 31 | await prisma.user.create({ 32 | data: { 33 | ...data, 34 | password: hashSync(data.password, 10), 35 | }, 36 | }); 37 | } catch (error) { 38 | console.log("ERROR OCCURED SIGNUP ACTION", error); 39 | if (error instanceof PrismaClientKnownRequestError) { 40 | console.log(error.code); 41 | switch (error.code) { 42 | case "P2002": 43 | return { error: "Email already exists" }; 44 | default: 45 | return { error: "An error occurred" }; 46 | } 47 | } 48 | return { error: "An error occurred" }; 49 | } 50 | redirect("/sign-in"); 51 | }; 52 | -------------------------------------------------------------------------------- /app/sign-up/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { signUpSchema } from "@/lib/form-schemas"; 3 | import { SubmitButton } from "@/components/SubmitButton"; 4 | import { signUpAction } from "@/lib/actions"; 5 | import Link from "next/link"; 6 | 7 | export default function SignUpPage() { 8 | const handleFormSubmit = async (formData: FormData) => { 9 | const formValues = { 10 | username: formData.get("username") as string, 11 | email: formData.get("email") as string, 12 | password: formData.get("password") as string, 13 | }; 14 | 15 | const { error } = await signUpSchema.safeParseAsync(formValues); 16 | if (error) { 17 | alert(error.issues[0].message); 18 | } 19 | 20 | const res = await signUpAction(formValues); 21 | if (res?.error) { 22 | alert(res.error); 23 | } 24 | }; 25 | 26 | return ( 27 |
28 | 29 | ◄ Home 30 | 31 |
32 |

Sign Up

33 |

40 | Demo app, please don't use your real email or password 41 |

42 | 43 | 44 | 45 | 46 | Create Account 47 | 48 | 49 | Already have an account? Sign In 50 | 51 |
52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js MongoDB Prisma Auth Template 2 | 3 | [**`🌐 App Demo`**](https://nextjs-mongodb-prisma-auth-template.vercel.app/) 4 | 5 |

6 | 7 |

8 | 9 | This is a template repository for building a Next.js application with MongoDB, Prisma, and Next Auth **V5**. 10 | 11 | ## Features 12 | 13 | - 🚀 Next Auth **V5** with user registration, login, and logout functionality 14 | - 🚀 Protected Routes 15 | - 🚀 Next.js framework for server-side rendering and client-side rendering 16 | - 🚀 MongoDB for database storage 17 | - 🚀 Prisma for database ORM 18 | 19 | ## Getting Started 20 | 21 | 1. Clone the repository 22 | 2. Install dependencies: `npm install` 23 | 3. Set up your environment variables by creating a `.env` `or` `.env.local` file based on the `.env.example` file. 24 | 4. Generate and DB Push Prisma Client 25 | ```bash 26 | npx prisma generate 27 | npx prisma db push 28 | ``` 29 | 5. Start the development server: `npm run dev` 30 | 31 | ## What you need to know 32 | 33 | - `auth.config.ts` `&&` `app/lib/actions.ts` handles auth logic 34 | - `/lib/form-schemas.ts` zod for form validation 35 | - `middleware.ts` handles protected routes 36 | 37 | 38 | ## More Starter Templates 39 | 40 | - [React Supabase Auth Template 🌟](https://github.com/mmvergara/react-supabase-auth-template) 41 | - [React Supabase ShadCN Auth Template](https://github.com/mmvergara/react-supabase-shadcn-auth-template) 42 | - [NextJs MongoDB Prisma Auth Template 🌟](https://github.com/mmvergara/nextjs-mongodb-prisma-auth-template) 43 | - [NextJs Discord Bot Template 🌟](https://github.com/mmvergara/nextjs-discord-bot-boilerplate) 44 | - [React Firebase🔥 Auth Template 🌟](https://github.com/mmvergara/react-firebase-auth-template) 45 | - [Golang Postgres Auth Template](https://github.com/mmvergara/golang-postgresql-auth-template) 46 | - [Vue Supabase Auth Template](https://github.com/mmvergara/vue-supabase-auth-starter-template) 47 | - [Remix Drizzle Auth Template](https://github.com/mmvergara/remix-drizzle-auth-template) 48 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | font-family: "Inter", sans-serif !important; 6 | } 7 | 8 | body { 9 | background-color: hsl(240, 4%, 9%); 10 | color: white; 11 | } 12 | 13 | main { 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | margin-top: 10vh; 18 | } 19 | 20 | a { 21 | text-decoration: none; 22 | } 23 | 24 | .header-text { 25 | display: flex; 26 | gap: 0.5em; 27 | align-items: center; 28 | padding: 0.5em 1em; 29 | font-weight: bold; 30 | background: none; 31 | text-align: center; 32 | } 33 | 34 | .main-container { 35 | display: flex; 36 | flex-direction: column; 37 | align-items: center; 38 | background-color: hsl(240, 4%, 12%); 39 | padding: 2em 2em; 40 | width: 100%; 41 | max-width: 500px; 42 | border-radius: 2px; 43 | box-shadow: 0 0 60px rgba(0, 0, 0, 0.2); 44 | border-top: 5px; 45 | border-width: 10px 0px 0px 0px; 46 | border-style: solid; 47 | border-color: #9faab8; 48 | } 49 | 50 | #github-repo-link { 51 | display: flex; 52 | gap: 0.5em; 53 | margin-top: 0; 54 | align-items: center; 55 | padding: 0.5em 1em; 56 | font-weight: bold; 57 | background: none; 58 | cursor: pointer; 59 | color: #9faab8; 60 | } 61 | 62 | #github-repo-link:hover { 63 | background-color: rgba(159, 170, 184, 0.1); 64 | } 65 | 66 | input { 67 | padding: 1rem; 68 | border-radius: 0.125rem; 69 | outline: none; 70 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); 71 | background-color: hsl(240, 3%, 9%); 72 | border: 0; 73 | font-size: 1rem; 74 | color: white; 75 | margin-top: 7px; 76 | width: 300px; 77 | } 78 | 79 | button, 80 | a { 81 | font-size: 16px; 82 | padding: 1em; 83 | background: none; 84 | cursor: pointer; 85 | color: white; 86 | background: #9faab8; 87 | border: none; 88 | border-radius: 0.125em; 89 | transition: background-color 0.3s; 90 | width: 300px; 91 | text-align: center; 92 | margin-top: 1em; 93 | display: flex; 94 | justify-content: center; 95 | align-items: center; 96 | height: 40px; 97 | font-weight: bold; 98 | } 99 | 100 | button:hover, 101 | a:hover { 102 | background: #8c99a9; 103 | } 104 | 105 | .home-link { 106 | margin-bottom: 1em; 107 | } 108 | 109 | .auth-link { 110 | background: transparent; 111 | color: #9faab8; 112 | } 113 | 114 | .auth-link:hover { 115 | background: transparent; 116 | text-decoration: underline; 117 | } 118 | 119 | #divider { 120 | width: 70%; 121 | height: 2px; 122 | background-color: #9faab8; 123 | margin: 1em 0; 124 | border-radius: 100%; 125 | } 126 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth, signOut } from "@/auth"; 2 | import Link from "next/link"; 3 | import { 4 | isRedirectError, 5 | redirect, 6 | } from "next/dist/client/components/redirect"; 7 | import { SubmitButton } from "@/components/SubmitButton"; 8 | 9 | export default async function Home() { 10 | const session = await auth(); 11 | return ( 12 |
13 |
14 |

NextJS MongoDB Prisma Auth

15 |

Current User : {session?.user?.email || "None"}

16 | {session?.user ? ( 17 |
{ 19 | "use server"; 20 | try { 21 | await signOut({ redirect: false }); 22 | } catch (err) { 23 | if (isRedirectError(err)) { 24 | console.error(err); 25 | throw err; 26 | } 27 | } finally { 28 | redirect("/"); 29 | } 30 | }} 31 | > 32 | 36 | Sign Out 37 | 38 |
39 | ) : ( 40 | Sign In 41 | )} 42 | Protected Page 🛡️ 43 |
44 | 50 | 58 | 62 | 63 | Github Repository 64 | 65 |
66 |
67 | ); 68 | } 69 | --------------------------------------------------------------------------------