Next.js は、フロントエンドとバックエンドの両方の機能を備えたアプリケーションを構築できる強力なフルスタック フレームワークです。非常に柔軟性があり、単純な静的 Web サイトから複雑な Web アプリまであらゆるものに使用できます。今日は、Next.js を使用して電子メールお問い合わせフォームを構築します。
フォームは Web サイトの重要な部分であり、ユーザーがアプリケーションを操作できるようになります。サインアップ、ログイン、フィードバックの提供、データ収集のいずれの場合でも、フォームはユーザー エクスペリエンスにとって不可欠です。フォームがなければ、フルスタック アプリケーションはユーザー入力を適切に収集して処理できません。
このブログでは、Next.js、Resend、および Zod (フォーム検証用) を使用して電子メール コンタクト フォームを作成する方法を説明します。プロジェクトのセットアップ、フォームの設計、フォーム送信の処理、別の API ルートの作成について説明します。最後には、Next.js アプリを構築してフォームを追加し、Web アプリが適切に動作し、使いやすくなる方法を理解できるようになります。
それでは、早速始めましょう。
Resend は、開発者向けの最新のメール API です。アプリケーションからの電子メール送信をシンプル、信頼性、拡張性に優れたものにするように設計されています。従来の電子メール サービスとは異なり、Resend は開発者を念頭に置いて構築されており、Next.js を含むさまざまなプログラミング言語やフレームワークとシームレスに統合する簡単な API を提供します。
Next.js フォーム プロジェクトでは、Resend を使用して電子メールを送信します。ユーザーがフォームを送信すると、Resend の API を使用して確認メールを送信するか、必要に応じてフォーム データを処理します。
Zod はデータのための強力なツールです。これは、データの形状を定義および確認するのに役立つ TypeScript ファーストのライブラリです。これは、データにルールを設定し、データを使用する前にそのルールに一致することを確認することだと考えてください。
TypeScript を使用している場合 (使用していない場合は、検討する必要があります!)、Zod は TypeScript とうまく連携します。スキーマから TypeScript 型を自動的に推論できるため、時間を大幅に節約できます。 TypeScript はコンパイル時に型をチェックしますが、Zod は実行時に型をチェックします。これは、静的型チェックをすり抜けてしまう可能性のあるデータの問題をキャッチできることを意味します。 Zod は、単純なフォーム入力から複雑な API 応答まで、あらゆる種類のデータ検証シナリオに使用できます。
必要な依存関係をすべて備えた Next.js プロジェクトをセットアップすることから始めましょう。タイプ セーフティには TypeScript、スタイル設定には Tailwind CSS、UI コンポーネントには Ant Design、フォーム検証には Zod、電子メール機能には Resend を使用します。
npx create-next-app@latest my-contact-form --typescript cd my-contact-form
yarn add antd zod resend react-icons
メールの送信には再送信を使用するため、再送信 API キーが必要です。サーバーを起動する前に、「再送信」に移動して API キーを取得しましょう。ここをクリックして再送信サイトに移動し、サインイン ボタンをクリックします。
サインインすると、このページにリダイレクトされます。ここには、フォームから受信したすべてのメールが表示されます。
ここで、「API キー」セクションをクリックします
そして、これをクリックして API キーを生成しますか?ボタン
次に、その API キーをコピーして安全に保管します。次に、VSCode を開き、ルート フォルダーに .env という名前の新しいファイルを作成します。そこに環境変数を追加します。
RESEND_API_KEY=yourapikeywillbehere
これで、このコマンドを使用してサーバーを実行することもできます。
yarn dev
まず、電子メール テンプレートを作成しましょう。これは、誰かが問い合わせフォーム経由でメールを送信したときに受け取るテンプレートになります。
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> );
この単純な React コンポーネントは、誰かが問い合わせフォームを送信したときに送信される電子メールの構造を定義します。 firstName と message という 2 つの小道具を必要とします。このコンポーネントは、名を使用してパーソナライズされた挨拶を作成し、送信されたメッセージを表示します。
こちら。 Resend API を使用して電子メール送信機能を実装する方法を見ていきます。
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
以上がResend と Zod を使用して Next.js で動的なメール連絡フォームを作成する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。