SupaLaunch is a SaaS boilerplate built using Supabase and Next.js. It includes authentication, Stripe payments, Postgres database, 20+ TailwindCSS themes, emails, OpenAI API streaming, file storage and more.
When you are building a Next.js application, you will need to create forms to collect user data. For example, it could be login/signup form or a contact form.
Since forms require user input, they are executed in the client code, i.e., in the browser. And you goal as a developer is to handle form submission. The standard way of doing it is sending a POST API request to your backend server. So it will require you to write a separate API route in Next.js to handle it.
Starting from Next.js 14, form submissions become easier thanks to server actions. Now you don't need to create a separate API route to handle form submissions. Instead, you can create a function called a server action
that will be executed on the server side.
Let's see how we can do it in practice. We will build a simple Login with Google
form.
First, let's create a form in Next.js client component. Note use client
on the top of the file to indicate that this component will be executed on the client side.
'use client'
import { login } from '@/actions'
export default function LoginForm() {
return (
<form className='flex justify-center p-5'>
<button
className='btn btn-primary w-full px-10'
formAction={login}
>
<img src="/images/google_logo.png" alt="google" className="w-6 h-6"/>
Log In with Google
</button>
</form>
)
}
We have a html form with a single button. When the button is clicked, the server action login
function will be executed thanks to formAction
attribute.
Now let's create a server action that will handle the form submission. The server action is a function that will be executed on the server side. It can be used to perform server-side operations like fetching data from a database, sending emails, etc.
In @/actions.ts file, create a function called login
that will handle the form submission. Note that you can put your server actions in any file, not necessarily in the actions.ts file.
Note use server
on the top of the file to indicate that this function will be executed on the server side.
'use server'
import {createClient} from "@supabase/supabase-js";
import {redirect} from "next/navigation";
import {revalidatePath} from "next/cache";
export async function login(formData: FormData) {
const supabase = createClient()
const redirectURL = '/auth/callback'
// Sign in with Google using Supabase Auth
const {data, error} = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: redirectURL,
scopes: 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
},
})
if (error) {
// Handle error
console.log('error', error)
redirect('/auth/error')
} else {
// Redirect to the URL returned by the server
revalidatePath('/', 'layout')
redirect(data.url)
}
}
In this function, we are using Supabase Auth to sign in with Google. If the sign-in is successful, we redirect the user to the URL returned by the server. If there is an error, we redirect the user to the error page.
The login
function takes a single argument formData
. This argument is an instance of the FormData
class. The FormData
class is a built-in class in JavaScript that represents form data. It is used to create a new FormData
object that can be passed to the server action.
When the form is submitted, the FormData
object is automatically created and passed to the server action. You can access the form data in the server action using the formData
argument.
Here is an example of how you can pass form data to the server action:
<form className='flex justify-center p-5'>
<label htmlFor="email">
Email
</label>
<input type="email" id="email" name="email" required />
<label htmlFor="password">
Password
</label>
<input type="password" id="password" name="password" required />
<button
className='btn btn-primary w-full px-10'
formAction={login}
>
Log In with Email
</button>
</form>
In this example, we have added two input fields for email and password. When the form is submitted, the FormData
object will contain the values of these input fields.
export async function login(formData: FormData) {
const email = formData.get('email')
const password = formData.get('password')
// Perform login with email and password
}
Instead of using the formAction
attribute, you can also call the server action directly from the form submission event handler. All you need to do is use action
attribute on the form element and add type="submit"
to the button element.
<form className='flex justify-center p-5' action={login}>
<label htmlFor="email">
Email
</label>
<input type="email" id="email" name="email" required />
<label htmlFor="password">
Password
</label>
<input type="password" id="password" name="password" required />
<button
className='btn btn-primary w-full px-10'
type="submit"
>
Log In with Email
</button>
</form>
That's it! Now you have a form in Next.js that handles form submission using server actions. You can use this approach to handle other form submissions as well. And you don't need to create a separate API route for each form submission.
You can see more examples of server actions in the Next.js documentation.