Custom Sign-in Page
To add a custom sign-in page, you’ll need to define the path to your page in the pages
object in your Auth.js configuration. Make sure a route / page actually exists at the path you’re defining here!
In additionally, we’ll have to export a map of provider.id
and provider.name
to easily consume in our custom page if we want to dynamically render the correct buttons, based on what we’ve defined in our auth.ts
configuration. Because you can pass your providers to the providers
array as both a function, or the result of calling that function, this example providerMap
handles both cases.
./auth.ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import Credentials from "next-auth/providers/credentials"
import type { Provider } from "next-auth/providers"
const providers: Provider[] = [
Credentials({
credentials: { password: { label: "Password", type: "password" } },
authorize(c) {
if (c.password !== "password") return null
return {
id: "test",
name: "Test User",
email: "test@example.com",
}
},
}),
GitHub,
]
export const providerMap = providers
.map((provider) => {
if (typeof provider === "function") {
const providerData = provider()
return { id: providerData.id, name: providerData.name }
} else {
return { id: provider.id, name: provider.name }
}
})
.filter((provider) => provider.id !== "credentials")
export const { handlers, auth, signIn, signOut } = NextAuth({
providers,
pages: {
signIn: "/signin",
},
})
We can now build our own custom sign in page.
app/signin/page.tsx
import { redirect } from "next/navigation"
import { signIn, auth, providerMap } from "@/auth.ts"
import { AuthError } from "next-auth"
export default async function SignInPage(props: {
searchParams: { callbackUrl: string | undefined }
}) {
return (
<div className="flex flex-col gap-2">
<form
action={async (formData) => {
"use server"
try {
await signIn("credentials", formData)
} catch (error) {
if (error instanceof AuthError) {
return redirect(`${SIGNIN_ERROR_URL}?error=${error.type}`)
}
throw error
}
}}
>
<label htmlFor="email">
Email
<input name="email" id="email" />
</label>
<label htmlFor="password">
Password
<input name="password" id="password" />
</label>
<input type="submit" value="Sign In" />
</form>
{Object.values(providerMap).map((provider) => (
<form
action={async () => {
"use server"
try {
await signIn(provider.id, {
redirectTo: props.searchParams?.callbackUrl ?? "",
})
} catch (error) {
// Signin can fail for a number of reasons, such as the user
// not existing, or the user not having the correct role.
// In some cases, you may want to redirect to a custom error
if (error instanceof AuthError) {
return redirect(`${SIGNIN_ERROR_URL}?error=${error.type}`)
}
// Otherwise if a redirects happens Next.js can handle it
// so you can just re-thrown the error and let Next.js handle it.
// Docs:
// https://nextjs.org/docs/app/api-reference/functions/redirect#server-component
throw error
}
}}
>
<button type="submit">
<span>Sign in with {provider.name}</span>
</button>
</form>
))}
</div>
)
}
Then when calling signIn
without any arguments anywhere in your application, the custom signin page will appear.