Next.js 15 Form Component: A Complete Guide
An in-depth exploration of the new Form Component in Next.js 15, covering implementation, best practices, and limitations
Following up on our previous blog on the useActionState
hook, I want to introduce the new form handling improvements in Next.js 15. During the Next.js Conf, they announced the release of the `next/forms component. This new component retains all the capabilities of a standard HTML form while introducing enhanced features that make it faster and significantly more developer-friendly.
Handling Form Submission: The Old Way vs. The New Way
To illustrate the improvements introduced in Next.js 15, let’s walk through how form submission worked before and how it works now with the new Form Component. We'll show how both approaches tackle the same problem: a search bar where users can type in a product name, like "iPhone." By setting the scene early, you’ll be able to clearly see the efficiency gained.
The Old Way of Doing Things
In previous versions of Next.js, you'd have to use HTML forms along with hooks like useRouter
from next/navigation
to manually push the URL, as well as manually gather the form data. It meant more code and a more complex flow. Instead of a few lines, you were easily looking at 20+ lines of repetitive boilerplate:
Let's break down the old way step-by-step:
- Importing Dependencies: We had to import
useRouter
fromnext/navigation
to programmatically change routes after form submission. - Handling Form Submission: Instead of relying on simple form action attributes, we needed to manually add an
onSubmit
handler to gather the form data and prevent the default HTML form behaviour (event.preventDefault()
). - Constructing Form Data: We used the
FormData
API to gather the form fields and their values, which added extra code. - Routing Logic: Using
useRouter
, we manually pushed the constructed URL (/search?query=${query}
) to simulate what would normally be a straightforward navigation. - Client Component Requirement: Since we were using
useRouter
, the entire component had to be a client component, adding complexity and potentially affecting performance.
The New Way of Doing Things
With the new Form Component, all that complexity is eliminated, making the developer experience much more enjoyable and the code far more readable and maintainable.
In contrast to the old way, here’s what the new implementation looks like:
Notice the difference:
- No Manual URL Construction: The action prop automatically updates the URL with the query parameters, which means no need for
useRouter
or explicit calls torouter.push()
. - Simplified Form Submission: There’s no need to use
event.preventDefault()
or manually gather form data usingFormData
. - Cleaner Code: The new implementation has fewer lines of code and no explicit routing logic, which makes it easier to maintain.
- No Hooks Requirement: No use of hooks, which means the component can remain server-side and avoid being forced into client mode.
Server Actions with the Form Component
Now, let’s talk about the second superpower of the Form Component—executing server actions. In Next.js, server actions are server-side functions triggered by form submissions. By passing a server action as the action
prop, your form can directly invoke server-side code, simplifying the process.
Here's how it works in practice:
- Import the server action into your component.
- Use the Form Component and assign the server action to the
action
prop.
Example:
In this scenario, when you click the "Create Post" button, the createPost
function runs on the server, handling the form data. This is incredibly useful for creating new resources, handling sensitive logic, or performing server-side mutations without writing an API endpoint explicitly.
Example Server Action
The use server
directive tells Next.js that this function must run on the server, making it a cleaner and more efficient way to manage server-side logic originating from form submissions.
String or Function?
To summarise, there are two ways to use the action
prop within the Form Component: one as a string and the other as a function. Below, we'll quickly go over the behaviour, use case, and considerations for each and then we will take a look on when to use each case.
action
as a String (URL)
- Behaviour: The form submits data using the GET method, appending form data to the URL as query parameters.
- Use Case: Ideal for simple forms where data can be exposed in the URL, such as search forms.
- Considerations:
- Data is visible in the URL, which may not be suitable for sensitive information.
- Limited control over data processing and validation.
action
as a Function
- Behaviour: The form submits data to a server-side function, allowing for custom handling, validation, and processing.
- Use Case: Suitable for forms requiring server-side logic, data validation, or when handling sensitive information.
- Example:
- Considerations:
- Offers enhanced control over data handling.
- Data is not exposed in the URL, ensuring better security for sensitive information.
Framework for Choosing the Right action
Type for Your Forms
Forms are one of the most fundamental components of web development. However, deciding how to handle their submission effectively can be tricky, especially when balancing usability, security, and server-side processing. The action attribute is key to determining where form data goes once submitted, but choosing between using action as a function or as a URL string can significantly impact your application's overall behavior.
This framework will guide you through making the right decision based on a set of key questions. Let’s dive into each consideration to determine the most appropriate approach for handling your form submissions.
1. Does the Form Handle Sensitive Information?
When handling sensitive information like passwords, personal details, or payment data, you should use action as a function. This prevents sensitive data from being exposed in the URL, thereby enhancing security. By keeping sensitive information out of the URL, you reduce the risk of it being logged in browser history or server logs, which could be potential security vulnerabilities.
If the form doesn’t handle sensitive information, such as a newsletter subscription, it is possibly acceptable to use action as a string. However, let's move on to the next question to gather more information.
2. Is Server-Side Validation or Processing Required?
For forms that require server-side logic, such as validating data or interacting with a database, using action as a function is the way to go. This gives you complete control over how data is validated and processed before taking further action. For example, ensuring a username is unique or confirming that submitted data meets specific criteria can be effectively handled server-side.
If the form is straightforward and doesn't require complex server-side validation, using action as a string can simplify the process. However, we are not done yet; there are still a few more questions to consider to be absolutely sure.
3. Is It Acceptable for Form Data to Be Visible in the URL?
In some cases, having form data visible in the URL is actually beneficial. For example, search bars often use action as a string (URL) to allow users to bookmark or share their search queries. If there is no sensitive data involved and the visibility of form data in the URL serves a functional purpose, this approach is ideal.
If form data contains personal or sensitive details that should not be publicly visible, using action as a function is the safer choice. This keeps data internal and away from the URL, ensuring user privacy and preventing exposure in logs or browser history.
4. Is the Form Simple, Like a Search Bar, Where Data Visibility in the URL Is Beneficial?
If your form is as simple as a search bar or filter, where having the parameters in the URL is advantageous, then using action as a string (URL) is a great fit. It allows users to easily bookmark the resulting page or share a link to their filtered results—features that improve user experience in specific contexts.
For more complex forms that do not benefit from having data in the URL, or for any form where security is a primary concern, using action as a function is preferable. This keeps the data internal and protects user information, which is essential for maintaining trust and privacy.
Choosing the right action type for your forms is all about understanding the nature of the data you are handling and the context in which the form is being used. By asking yourself these key questions, you can make an informed decision that balances security, usability, and functionality. Whether you’re handling sensitive data, requiring server-side validation, or simply enabling users to share a link, the right action type can ensure your forms are robust and user-friendly.
I made a quick component that will help you go through the questions and decide which action type to use. Simply answer each question, and the component will suggest whether to use action as a function or as a URL, making your decision-making process more intuitive.
Form Implementation
Does the form handle sensitive information?
Why You Should Use the Form Component
There is no right or wrong when choosing between the old way or the new Form Component. What I will say is that the new Form Component is a massive quality-of-life improvement for any Next.js developer. It really reduces the boilerplate, simplifies state management, and integrates beautifully into client-side navigation and server actions. If you have a small app with a one-off form, you can still opt for the older approach. However, if you're starting a web app using Next.js 15 and expect more complex state management in the future, I highly recommend using the Form Component.
Limitations and Strategies for Using the Form Component
As with any great concept that reduces the workload for developers, it also reduces flexibility, and the Next.js Form Component is no different. Let's explore some of the potential challenges when using the Form Component.
1. Unsupported Attributes
The <Form>
component does not support the method
, encType
, and target
attributes, as they can override its intended behaviour. If your form requires these attributes, use the standard HTML <form>
element instead. This approach ensures full compatibility with these attributes and allows for the desired form behaviour.
2. Handling File Inputs
When using <input type="file">
within the <Form>
component and setting the action
prop as a string, the form submits only the filename, not the actual file content. For forms that need to handle file uploads, opt for the standard HTML <form>
element with the appropriate method
and encType
attributes. This setup ensures that the full file data is transmitted during submission.
3. Overriding Default Behaviour with onSubmit
Attaching an onSubmit
event handler and calling event.preventDefault()
will override the default behaviour of the <Form>
component, such as client-side navigation. If you need to use the onSubmit
event, be cautious about calling event.preventDefault()
. Ensure that any custom submission logic aligns with the desired form behaviour and does not conflict with the component's default handling.
4. Prefetching Limitations
When the action
prop is a function (e.g., a Server Action), the <Form>
component cannot automatically prefetch shared UI components. To mitigate potential performance issues, consider manually prefetching necessary components or data when using function-based actions. This proactive approach can help maintain optimal performance.
5. Potential for Unintended Behaviour
Misconfiguring the action
prop or misunderstanding its dual functionality (string vs. function) can lead to unintended form submission behaviour. Ensure that the action
prop is set appropriately based on the desired form handling logic. Clearly define whether the form should navigate to a URL or execute a server-side function, and configure the action
prop accordingly.
As you can see, there are a few gotchas and limitations, but nothing that would prevent you from using the Form Component entirely in your app.
Final Words
The Form Component in Next.js 15 is all about simplifying and optimising the developer experience. Whether you’re navigating to new pages with search parameters or triggering server-side actions directly, this new feature offers a more efficient, cleaner solution. If you’re building apps with Next.js 15, I would highly recommend implementing it or even starting a small project to play around with it and see whether it fits your workflow.