2 min read

Build a custom login page with Next.js, Tailwind CSS, and Next-Auth

Build a custom login page with Next.js, Tailwind CSS, and Next-Auth

Next-Auth provides a very basic login page for you out of the box. However, once you ship your product to production, you're going to want to elevate that experience and have the login page really pop. To do that, you can create a custom login page and tell Next-Auth where to find it.

This guide walks through creating the page and using Next-Auth to log in.

Technologies

It's okay if you have a slightly different technology stack. Just adapt the code and logic to your specific use case.

Guide

Follow along, or download the entire project.

Link to source code: https://github.com/ethanmick/nextauth-custom-login

The first thing to do is to build the Login page in the app directory:

import Link from 'next/link'
import { Form as LoginForm } from './form'

export default function LoginPage() {
  return (
    <div className="h-screen w-screen flex justify-center items-center bg-slate-100">
      <div className="sm:shadow-xl px-8 pb-8 pt-12 sm:bg-white rounded-xl space-y-12">
        <h1 className="font-semibold text-2xl">Login</h1>
        <LoginForm />
        <p className="text-center">
          Need to create an account?{' '}
          <Link className="text-indigo-500 hover:underline" href="/register">
            Create Account
          </Link>{' '}
        </p>
      </div>
    </div>
  )
}

app/register/page.tsx

The form is interactive, so it needs to be a client component. Create a new file in the same app/login directory called form.tsx that you can import:

'use client'

import { Alert } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { signIn } from 'next-auth/react'
import { useRouter, useSearchParams } from 'next/navigation'
import { useState } from 'react'

export const Form = () => {
  const router = useRouter()
  const searchParams = useSearchParams()
  const callbackUrl = searchParams.get('callbackUrl') || '/dashboard'
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [error, setError] = useState('')

  const onSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    try {
      const res = await signIn('credentials', {
        redirect: false,
        email,
        password,
        callbackUrl
      })
      console.log('Res', res)
      if (!res?.error) {
        router.push(callbackUrl)
      } else {
        setError('Invalid email or password')
      }
    } catch (err: any) {}
  }

  return (
    <form onSubmit={onSubmit} className="space-y-12 w-full sm:w-[400px]">
      <div className="grid w-full items-center gap-1.5">
        <Label htmlFor="email">Email</Label>
        <Input
          className="w-full"
          required
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          id="email"
          type="email"
        />
      </div>
      <div className="grid w-full items-center gap-1.5">
        <Label htmlFor="password">Password</Label>
        <Input
          className="w-full"
          required
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          id="password"
          type="password"
        />
      </div>
      {error && <Alert>{error}</Alert>}
      <div className="w-full">
        <Button className="w-full" size="lg">
          Login
        </Button>
      </div>
    </form>
  )
}

The components referenced can be copied and pulled in from here: https://ui.shadcn.com/docs/primitives/accordion

Source Code

You can find the source on my GitHub account: https://github.com/ethanmick/nextauth-custom-login