Next.js ialah rangka kerja susunan penuh yang berkuasa yang membolehkan kami membina aplikasi dengan ciri bahagian hadapan dan bahagian belakang. Ia sangat fleksibel dan boleh digunakan untuk segala-galanya daripada tapak web statik mudah kepada apl web yang kompleks. Hari ini, kami akan menggunakan Next.js untuk membina borang hubungan e-mel.
Borang ialah bahagian penting dalam mana-mana tapak web, membenarkan pengguna berinteraksi dengan aplikasi. Sama ada untuk mendaftar, log masuk, memberi maklum balas atau mengumpul data, borang adalah penting untuk pengalaman pengguna. Tanpa borang, aplikasi tindanan penuh tidak akan dapat mengumpulkan dan memproses input pengguna dengan betul.
Dalam blog ini, saya akan menunjukkan kepada anda cara membuat borang hubungan e-mel menggunakan Next.js, Hantar Semula dan Zod (untuk pengesahan borang). Kami akan meliputi penyediaan projek, mereka bentuk borang, mengendalikan penyerahan borang dan mencipta laluan API yang berasingan. Pada akhirnya, anda akan tahu cara membina dan menambah borang pada apl Next.js anda, memastikan apl web anda berfungsi dengan baik dan mudah digunakan.
Jadi, tanpa berlengah lagi, mari kita mulakan.
Hantar semula ialah API e-mel moden untuk pembangun. Ia direka bentuk untuk menjadikan penghantaran e-mel daripada aplikasi anda mudah, boleh dipercayai dan berskala. Tidak seperti perkhidmatan e-mel tradisional, Hantar Semula dibina dengan mengambil kira pembangun, menawarkan API mudah yang disepadukan dengan lancar dengan pelbagai bahasa pengaturcaraan dan rangka kerja, termasuk Next.js.
Dalam projek borang Next.js kami, kami akan menggunakan Hantar Semula untuk menghantar e-mel. Apabila pengguna menyerahkan borang, kami akan menggunakan API Hantar Semula untuk menghantar e-mel pengesahan atau memproses data borang seperti yang diperlukan.
Zod ialah alat yang berkuasa untuk data anda. Ia adalah perpustakaan pertama TypeScript yang membantu anda menentukan dan menyemak bentuk data anda. Anggap ia sebagai menetapkan peraturan untuk data anda dan kemudian pastikan data itu sepadan dengan peraturan tersebut sebelum anda menggunakannya.
Jika anda menggunakan TypeScript (dan jika tidak, anda harus mempertimbangkannya!), Zod bermain dengan baik dengannya. Ia secara automatik boleh membuat kesimpulan jenis TypeScript daripada skema anda, yang merupakan penjimat masa yang besar. Walaupun TypeScript menyemak jenis pada masa kompilasi, Zod melakukannya pada masa jalan. Ini bermakna anda boleh menangkap isu data yang mungkin tergelincir melalui pemeriksaan jenis statik. Anda boleh menggunakan Zod untuk semua jenis senario pengesahan data, daripada input bentuk ringkas kepada respons API yang kompleks.
Mari mulakan dengan menyediakan projek Next.js kami dengan semua kebergantungan yang diperlukan. Kami akan menggunakan TypeScript untuk keselamatan jenis, Tailwind CSS untuk penggayaan, Ant Design untuk komponen UI, Zod untuk pengesahan borang dan Hantar Semula untuk fungsi e-mel.
npx create-next-app@latest my-contact-form --typescript cd my-contact-form
yarn add antd zod resend react-icons
Untuk menghantar e-mel, kami akan menggunakan Hantar Semula, jadi kami memerlukan kunci API Hantar Semula. Sebelum memulakan pelayan kami, mari pergi ke Hantar Semula dan dapatkan kunci API kami. Klik di sini untuk pergi ke tapak Hantar Semula dan klik butang log masuk.
Selepas melog masuk, anda akan diubah hala ke halaman ini. Di sini, anda akan melihat semua e-mel yang anda terima daripada borang anda.
Di sini, klik pada bahagian Kunci API
Dan, jana kunci API dengan mengklik pada ini ? butang
Sekarang, salin kunci API itu dan pastikan ia selamat. Seterusnya, buka VSCode dan buat fail baharu bernama .env dalam folder akar anda. Tambahkan pembolehubah persekitaran di sana.
RESEND_API_KEY=yourapikeywillbehere
Kini anda juga boleh menjalankan pelayan anda menggunakan arahan ini.
yarn dev
Mari mulakan dengan mencipta templat e-mel. Ini akan menjadi templat yang anda terima apabila seseorang menghantar e-mel kepada anda melalui borang hubungan.
import * as React from 'react'; interface EmailTemplateProps { firstName: string; message: string; } export const EmailTemplate: React.FC<EmailTemplateProps> = ({ firstName, message, }) => ( <div> <h1>Hello, I am {firstName}!</h1> <p>You have received a new message from your Portfolio:</p> <p>{message}</p> </div> );
Komponen React ringkas ini mentakrifkan struktur e-mel yang akan dihantar apabila seseorang menyerahkan borang hubungan. Ia memerlukan dua prop: nama pertama dan mesej. Komponen mencipta ucapan diperibadikan menggunakan nama pertama dan memaparkan mesej yang diserahkan.
Di sini. kita akan melihat cara melaksanakan fungsi penghantaran e-mel menggunakan API Hantar Semula.
First, let's look at where this code lives in our Next.js project:
app/ ├── api/ │ └── v1/ │ └── send/ │ └── route.ts
This structure follows Next.js 13's App Router convention, where API routes are defined as route handlers within the app directory.
This is our complete API route code ?
import { EmailTemplate } from 'app/components/email-template'; import { NextResponse } from 'next/server'; import { Resend } from 'resend'; import { v4 as uuid } from 'uuid'; const resend = new Resend(process.env.RESEND_API_KEY); export async function POST(req: Request) { try { const { name, email, subject, message } = await req.json(); const { data, error } = await resend.emails.send({ from: 'Contact Form <onboarding@resend.dev>', to: 'katare27451@gmail.com', subject: subject || 'New Contact Form Submission', reply_to: email, headers: { 'X-Entity-Ref-ID': uuid(), }, react: EmailTemplate({ firstName: name, message }) as React.ReactElement, }); if (error) { return NextResponse.json({ error }, { status: 500 }); } return NextResponse.json({ data, message: 'success' }, { status: 200 }); } catch (error) { console.error('Error processing request:', error); return NextResponse.json({ error: 'Failed to process request' }, { status: 500 }); } }
Now, let's examine each part of the code:
import { EmailTemplate } from 'app/components/email-template'; import { NextResponse } from 'next/server'; import { Resend } from 'resend'; import { v4 as uuid } from 'uuid';
These import statements bring in the necessary dependencies:
const resend = new Resend(process.env.RESEND_API_KEY);
Here, we initialize the Resend client with our API key. It's crucial to keep this key secret, so we store it in an environment variable.
export async function POST(req: Request) { // ... (code inside this function) }
This exports an async function named POST, which Next.js will automatically use to handle POST requests to this route.
const { name, email, subject, message } = await req.json();
We extract the form data from the request body. This assumes the client is sending a JSON payload with these fields.
const { data, error } = await resend.emails.send({ from: 'Contact Form <onboarding@resend.dev>', to: 'katare27451@gmail.com', subject: subject || 'New Contact Form Submission', reply_to: email, headers: { 'X-Entity-Ref-ID': uuid(), }, react: EmailTemplate({ firstName: name, message }) as React.ReactElement, });
This is where we'll get our emails! We use Resend's send method to dispatch the email:
if (error) { return NextResponse.json({ error }, { status: 500 }); } return NextResponse.json({ data, message: 'success' }, { status: 200 });
Here, we handle the response from Resend. If there's an error, we return a 500 status with the error details. Otherwise, we send a success response.
catch (error) { console.error('Error processing request:', error); return NextResponse.json({ error: 'Failed to process request' }, { status: 500 }); }
This catch block handles any unexpected errors, logs them, and returns a generic error response.
And that's it! We've set up our API route. The only thing left is to set up our logic and UI. Let's do that too ?
In your app directory, create a new folder named contact-form and inside this folder, create a file named page.tsx.
app/ ├── contact-form/ │ └── page.tsx
First, import all necessary components from Ant Design, Next.js, and React Icons. We also import Zod for form validation.
import React from 'react'; import { Form, Input, Button, message, Space, Divider, Typography } from 'antd'; import Head from 'next/head'; import { FaUser } from 'react-icons/fa'; import { MdMail } from 'react-icons/md'; import { FaMessage } from 'react-icons/fa6'; import { z } from 'zod'; import Paragraph from 'antd/es/typography/Paragraph';
Now, let's create our UI, and then we'll move on to the logic. Our form will look something like this.?
In your page.tsx, after all the import statements, define a component and add a return statement first.
const ContactPage: React.FC = () => { return ( <div className="max-w-3xl w-full space-y-8 bg-white p-10 rounded-xl shadow-2xl"> /* our code will be here */ </div> ); }; export default ContactPage;
Currently, we have just a simple div with a few tailwind styling now, we'll first add our heading part.
... <div> <h2 className="mt-1 text-center text-3xl font-extrabold text-gray-900">Get in Touch</h2> <p className="mt-1 mb-4 text-center text-sm text-gray-600"> I'd love to hear from you! Fill out the form below to get in touch. </p> </div> ...
Now, let's add our input fields
... <Form form={form} name="contact" onFinish={onFinish} layout="vertical" className="mt-8 space-y-6" > <Form.Item name="name" rules={[{ required: true, message: 'Please input your name!' }]} > <Input prefix={<FaUser className="site-form-item-icon" />} placeholder="Your Name" size="large" /> </Form.Item> <Form.Item name="email" rules={[ { required: true, message: 'Please input your email!' }, { type: 'email', message: 'Please enter a valid email!' } ]} > <Input prefix={<MdMail className="site-form-item-icon" />} placeholder="Your Email" size="large" /> </Form.Item> <Form.Item name="subject" rules={[{ required: true, message: 'Please input a subject!' }]} > <Input prefix={<FaMessage className="site-form-item-icon" />} placeholder="Subject" size="large" /> </Form.Item> <Form.Item name="message" rules={[{ required: true, message: 'Please input your message!' }]} > <TextArea placeholder="Your Message" autoSize={{ minRows: 4, maxRows: 8 }} /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit" className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" disabled={isSubmitting} > {isSubmitting ? 'Sending...' : 'Send Message'} </Button> </Form.Item> </Form> ...
Here, in the above code firstly we added a Form Component. This is the main Form component from Ant Design. It has the following props:
<Form form={form} name="contact" onFinish={onFinish} layout="vertical" className="mt-8 space-y-6" > {/* Form items */} </Form>
Then, we added a Form Items. Each Form.Item represents a field in the form. Let's look at the "name" field as an example.
<Form.Item name="name" rules={[{ required: true, message: 'Please input your name!' }]} > <Input prefix={<FaUser className="site-form-item-icon" />} placeholder="Your Name" size="large" /> </Form.Item>
Similarly, we have Email and other fields.
<Form.Item name="email" rules={[ { required: true, message: 'Please input your email!' }, { type: 'email', message: 'Please enter a valid email!' } ]} > <Input prefix={<MdMail className="site-form-item-icon" />} placeholder="Your Email" size="large" /> </Form.Item>
This field has an additional rule to ensure the input is a valid email address.
Subject and Message Fields: These are similar to the name field, with the message field using a TextArea component for multi-line input.
Then, we have a Submit Button to submit our form
<Form.Item> <Button type="primary" htmlType="submit" className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" disabled={isSubmitting} > {isSubmitting ? 'Sending...' : 'Send Message'} </Button> </Form.Item>
This is the submit button for the form. It's disabled when isSubmitting (we'll add this state in our next section) is true, and its text changes to "Sending..." during submission.
So, in the logic part, we have a few things to do:
We'll start with setting up our schema first.
// Zod schema for form validation const contactSchema = z.object({ name: z.string().min(4, 'Name must be at least 4 characters').max(50, 'Name must not exceed 50 characters'), email: z.string().email('Invalid email address').regex(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/, "Email must be a valid format"), subject: z.string().min(5, 'Subject must be at least 5 characters').max(100, 'Subject must not exceed 100 characters'), message: z.string().min(20, 'Message must be at least 20 characters').max(1000, 'Message must not exceed 1000 characters'), }); type ContactFormData = z.infer<typeof contactSchema>;
This part defines a Zod schema for form validation. As we already learned, Zod is a TypeScript-first schema declaration and validation library. The contactSchema object defines the structure and validation rules for the contact form:
The ContactFormData type is inferred from the Zod schema, providing type safety for the form data.
Now, let's add 2 new states and implement our onFinish function.
const ContactPage: React.FC = () => { const [form] = Form.useForm<ContactFormData>(); const [isSubmitting, setIsSubmitting] = React.useState(false); const onFinish = async (values: ContactFormData) => { setIsSubmitting(true); try { contactSchema.parse(values); const response = await fetch('/api/v1/send', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(values), }); if (!response.ok) { message.error('Failed to send message. Please try again.'); setIsSubmitting(false); } const data = await response.json(); if (data.message === 'success') { message.success('Message sent successfully!'); setIsSubmitting(false); form.resetFields(); } else { throw new Error('Failed to send message'); } } catch (error) { if (error instanceof z.ZodError) { error.errors.forEach((err) => { message.error(err.message); setIsSubmitting(false); }); } else { message.error('Failed to send message. Please try again.'); setIsSubmitting(false); } } finally { setIsSubmitting(false); } };
This part defines the ContactPage functional component:
This setup ensures that the form data is validated on both the client-side (using Antd's form validation) and the server-side (using Zod schema validation) before sending the data to the server. It also manages the submission state and provides user feedback through success or error messages.
And, this is the complete code of our contact-form file ?
"use client"; import React from 'react'; import { Form, Input, Button, message, Space, Divider, Typography } from 'antd'; import Head from 'next/head'; import { FaUser } from 'react-icons/fa'; import { MdMail } from 'react-icons/md'; import { FaMessage } from 'react-icons/fa6'; import { z } from 'zod'; import { Container } from 'app/components/container'; import Paragraph from 'antd/es/typography/Paragraph'; const { TextArea } = Input; const { Text } = Typography; // Zod schema for form validation const contactSchema = z.object({ name: z.string().min(4, 'Name must be at least 4 characters').max(50, 'Name must not exceed 50 characters'), email: z.string().email('Invalid email address').regex(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/, "Email must be a valid format"), subject: z.string().min(5, 'Subject must be at least 5 characters').max(100, 'Subject must not exceed 100 characters'), message: z.string().min(20, 'Message must be at least 20 characters').max(1000, 'Message must not exceed 1000 characters'), }); type ContactFormData = z.infer<typeof contactSchema>; const ContactPage: React.FC = () => { const [form] = Form.useForm<ContactFormData>(); const [isSubmitting, setIsSubmitting] = React.useState(false); const onFinish = async (values: ContactFormData) => { setIsSubmitting(true); try { contactSchema.parse(values); const response = await fetch('/api/v1/send', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(values), }); if (!response.ok) { message.error('Failed to send message. Please try again.'); setIsSubmitting(false); } const data = await response.json(); if (data.message === 'success') { message.success('Message sent successfully!'); setIsSubmitting(false); form.resetFields(); } else { throw new Error('Failed to send message'); } } catch (error) { if (error instanceof z.ZodError) { error.errors.forEach((err) => { message.error(err.message); setIsSubmitting(false); }); } else { message.error('Failed to send message. Please try again.'); setIsSubmitting(false); } } finally { setIsSubmitting(false); } }; return (); }; export default ContactPage;Get in Touch
I'd love to hear from you! Fill out the form below to get in touch.
} placeholder="Your Name" size="large" /> } placeholder="Your Email" size="large" /> } placeholder="Subject" size="large" />
Till now, we're all set, now it's time to run and test our application.
Start your server:
yarn dev
First, let's try to hit the endpoint without filling out the form. As expected, the API doesn't get called, and we receive error messages.
Now, let's fill out the form
and hit the send button. It's in process.
Here we go, the message is sent. The sender receives a notification saying "Message sent," and the form is also refreshed.
The receiver also gets the message?
And that's it. We have successfully built an email contact form in Next.js using Resend and Zod.
In this article, we built a contact form using Next.js and implemented features like form validation with Zod and email functionality with Resend. We start by setting up the Next.js project, configuring necessary dependencies, and managing environment variables for secure API access.
Then, we designed the email template, set up an API route for handling email submissions, and implemented the frontend form with Ant Design components.
If you want to see a live preview of it, you can check it out here. I have implemented the same form in my portfolio.
Thanks for reading this blog. If you learned something from it, please like and share it with your friends and community. I write blogs and share content on JavaScript, TypeScript, Open Source, and other web development-related topics. Feel free to follow me on my socials. I'll see you in the next one. Thank You :)
GitHub
Atas ialah kandungan terperinci Cara Membuat Borang Hubungan E-mel Dinamik dalam Next.js Menggunakan Hantar Semula dan Zod. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!