Deploy your dashboard in minutes using with Typescript, Next 14 and Supabase.
This example uses Supabase for database and authentication.
We are going to build admin dashboard with handling authentication on server side.
Create a new Next.js project:
Terminal
npx create-next-app@latest my-app --typescript --tailwind --eslint
Install Supabase client library
Terminal
npm install @supabase/supabase-js
Then install Supabase Server Side Auth
Terminal
npm install @supabase/ssr
Here’s the basic file structure for this example. If you don’t need the registration feature, you can skip the confirm folder.
app/
├── page.tsx
├── globals.css
├── layout.tsx
├── login/
│ ├── page.tsx
│ └── actions.ts
├── auth/
│ ├── confirm/ // Add this if you need register
│ │ └── route.ts
│ └── signout/
│ └── route.ts
└── dashboard/
└── page.tsx
utils/
└── supabase/
├── middleware.ts
├── client.ts
└── server.ts
middleware.ts
.env.local
Get your API url and anon key at Supabase. After that, create .env.local
and middleware.ts
at the project root.
.env.local
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
middleware.ts
import { type NextRequest } from 'next/server'
import { updateSession } from '@/utils/supabase/middleware'
export async function middleware(request: NextRequest) {
// update user's auth session
return await updateSession(request)
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* Feel free to modify this pattern to include more paths.
*/
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
}
Next, create a middleware.ts
file inside the utils/supabase
folder. This file will handle the session update logic, which will be used by the middleware.ts
file in the project root, following Next.js conventions.
utils/supabase/middleware.ts
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function updateSession(request: NextRequest) {
let supabaseResponse = NextResponse.next({
request,
})
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))
supabaseResponse = NextResponse.next({
request,
})
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
// refreshing the auth token
await supabase.auth.getUser()
return supabaseResponse
}
Next, set up the login page by creating page.tsx
inside the app/login
folder.
app/login/page.tsx
import { login, signup } from './actions'
export default function LoginPage() {
return (
<form>
<label htmlFor="email">Email:</label>
<input id="email" name="email" type="email" required />
<label htmlFor="password">Password:</label>
<input id="password" name="password" type="password" required />
<button formAction={login}>Log in</button>
<button formAction={signup}>Sign up</button>
</form>
)
}
Then, create actions.ts
in the app/login
folder, which will contain the signup and login actions.
app/login/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { createClient } from '@/utils/supabase/server'
export async function login(formData: FormData) {
const supabase = createClient()
// type-casting here for convenience
// in practice, you should validate your inputs
const data = {
email: formData.get('email') as string,
password: formData.get('password') as string,
}
const { error } = await supabase.auth.signInWithPassword(data)
if (error) {
redirect('/error')
}
revalidatePath('/', 'layout')
redirect('/dashboard')
}
export async function signup(formData: FormData) {
const supabase = createClient()
// type-casting here for convenience
// in practice, you should validate your inputs
const data = {
email: formData.get('email') as string,
password: formData.get('password') as string,
}
const { error } = await supabase.auth.signUp(data)
if (error) {
redirect('/error')
}
revalidatePath('/', 'layout')
redirect('/dashboard')
}
If you need a registration confirmation, create route.ts
inside the auth/confirm
folder.
app/auth/confirm/route.ts
import { type EmailOtpType } from '@supabase/supabase-js'
import { type NextRequest, NextResponse } from 'next/server'
import { createClient } from '@/utils/supabase/server'
// Creating a handler to a GET request to route /auth/confirm
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const token_hash = searchParams.get('token_hash')
const type = searchParams.get('type') as EmailOtpType | null
const next = '/account'
// Create redirect link without the secret token
const redirectTo = request.nextUrl.clone()
redirectTo.pathname = next
redirectTo.searchParams.delete('token_hash')
redirectTo.searchParams.delete('type')
if (token_hash && type) {
const supabase = createClient()
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
redirectTo.searchParams.delete('next')
return NextResponse.redirect(redirectTo)
}
}
// return the user to an error page with some instructions
redirectTo.pathname = '/error'
return NextResponse.redirect(redirectTo)
}
Next, create the signout page in auth/signout
.
app/auth/signout/route.ts
import { createClient } from '@/utils/supabase/server'
import { revalidatePath } from 'next/cache'
import { type NextRequest, NextResponse } from 'next/server'
export async function POST(req: NextRequest) {
const supabase = createClient()
// Check if a user's logged in
const {
data: { user },
} = await supabase.auth.getUser()
if (user) {
await supabase.auth.signOut()
}
revalidatePath('/', 'layout')
return NextResponse.redirect(new URL('/login', req.url), {
status: 302,
})
}
Finally, create the dashboard/page.tsx
.
app/dashboard/page.tsx
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export default async function Account() {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
// redirect to login if user is not logged in
if (!user) {
return redirect("/login");
}
return <h1>Admin Dashboard</h1>;
}
Completed:
Next Supabase