


















In this post, you will learn how to build a sign-up form using the Next.js App Router and the following technologies:
By the end of this guide, you will have a fully functional and secure sign-up form with the following features:
We won't be building the sign-up form step-by-step. Instead, you'll find the complete source code for the post on GitHub. I will guide you through the key parts of the code, explaining how each section functions and contributes to the final product.
Let's begin with the database schema, as it defines the structure of the sign-up form and serves as its foundation.
I'm using Drizzle with Postgres, but one of the advantages of using an ORM like Drizzle is its flexibility - you can adapt it to work with almost any database with minimal adjustments.
The table includes these columns: id, createdAt, email, and passwordHash.
An important aspect often overlooked is ensuring emails are stored as unique and case-insensitive. While PostgreSQL offers the citext module for this purpose, I've opted for an index using the lower function. This approach keeps everything within the application code and avoids the need to run additional PostgreSQL queries.
While basic constraints like length are useful at the database level, validations like email format are best handled in the application layer. In the next section, we'll explore using Zod to define and validate the email and other inputs before storing them in the database.
It's important to validate user input on both the frontend (in the client component) and the backend (in the server function):
Instead of duplicating validation code on the server and client, we use Zod to define the "shape" of a valid form in one place. By exporting the schema, it can be referenced on both the server and client and we keep the code nice and DRY.
A server action is a server-side function that can be called directly from client components. This allows you to run backend code, such as database queries and mutations, without needing to create separate API endpoints.
A common security oversight with server functions is assuming client-side validation is sufficient. However, server functions are essentially HTTP endpoints and a malicious actor could send invalid data directly using a tool like cURL. This may lead to inconsistencies in your database and could even pose a security risk. For this reason, we use Zod to validate all incoming data on the server, even though we already have client-side validation in place.
The server function checks for existing users by querying the database with the provided email. If a user is found, it returns a field-level error message stating: "The email you entered has already been taken".
In the server action above, we first hash the password before storing it in the database:
What is hashing and why is it important?
Storing passwords in plain text creates a significant security vulnerability. If an attacker gains access to your database through a data breach, they immediately have access to every user's account. Worse yet, since many people reuse passwords across services, compromised credentials could lead to breaches of users' accounts on other platforms.
This is where password hashing becomes crucial. A hash function transforms a password into an irreversible string of characters. When a user attempts to sign in, the system hashes their input password and compares it with the stored hash. If they match, you know the credentials are valid. This all happens without ever storing or exposing the actual password.
The code uses Argon2 for password hashing, which is considered one of the most secure hashing algorithms available today. While older algorithms like MD5 were once common, they've proven vulnerable to reverse-engineering attacks. Other popular options like Bcrypt are still secure, but Argon2 offers additional benefits - it's memory-hard (making it resistant to specialized hardware attacks) and was specifically designed to be future-proof against advances in password cracking technology.
While server-side validation is essential for security, relying on it alone creates a suboptimal user experience. Without client-side validation, users would need to submit the form to see if their input was valid - an experience that feels clunky and outdated.
The SignUpForm component uses React Hook Form to provide immediate, dynamic feedback as users type.
By passing the same Zod schema we use on the server to React Hook Form's zodResolver, we get automatic validation of password strength requirements and email format.
This creates a layered validation approach - immediate client-side feedback for a smooth user experience, backed by robust server-side validation for security.
As an added benefit, if JavaScript is disabled, the form gracefully falls back to server-side validation, displaying errors returned from the server function via useActionState.
This concludes our guide to building a secure sign-up form with Next.js, React Hook Form, and Argon2. You now have a solid foundation with robust form validation and proper password hashing. Additionally, the form is built using progressive enhancement, meaning it works even without JavaScript. This means you'll never miss a potential sign-up, even if JavaScript fails to load due to network issues, browser settings, or extensions.
While this is a good start, production-ready sign-up forms usually require more sophisticated features. Here are some advanced capabilities to consider for your implementation:
User experience improvements:
Security measures:
+, =, or #.Clerk is a user management and authentication platform, so it might surprise you that we're publishing an article that explains how to implement user registration in Next.js yourself.
While implementing the sophisticated features listed above from scratch is possible, it requires significant development effort and security expertise. If these advanced features are important for your application but you don't want to build them yourself, consider using a complete user management and authentication platform like Clerk that provides these capabilities out of the box.
In addition to sign-up, Clerk provides sign-in and manages the entire session, allowing you to authenticate access to pages and access information about the current user wherever you need it.
Learn how to add not only a sign-up form but complete sign-in and session management in minutes:
The best part? Clerk uses components as the API.
Instead of building your own form component and manually building all the necessary logic, you can just drop a Next.js <SignUp /> component in your page like so:
Clerk's component-driven approach makes setup incredibly easy. You can further customise your sign-up process and manage advanced features directly from the Clerk dashboard once you create a free application following the link below.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。