Next.js로 포트폴리오 만들기


EmailJS 세팅하기

  1. EmailJS 에 접속하여 회원가입 및 로그인을 한다.

  2. Add New Service 클릭!

image

  1. 보낼 이메일 서비스 클릭! » Create Service 클릭!

여기서 정한 이메일 주소에서 가입한 내 주소로 메일을 보내준다. (결국 내가 나에게 보냄)

image

  1. Email Templates 클릭 ! » 마찬가지로 Create New Template 클릭!

image

image

여기에서 안에 있는 단어가 코딩할 때, name=”“으로 작성해주면, 작성자가 input 안에 넣은 값을 메일 내용에 넣어서 보내준다.

나는 default의 내용과 살짝 다르게 변형시켰는데, 나는 포트폴리오에 넣는 contact라서 내용을 더 예쁘게 다듬지는 않았다.

NextJS에 적용하기

  1. KEY와 VALUE를 .env 파일에 작성해준다.

값들은 모두 EmailJS 사이트에서 찾아볼 수 있다. menu에서 Email Services, Email Templates, Account 에서 각각 찾아보면된다.

  • 여기에서 개인키는 private 키가 아니라 public 키를 찾아서 넣어주면 된다.
NEXT_PUBLIC_MAIL_TEMPLATE_KEY = Template_ID
NEXT_PUBLIC_NEXT_PUBLIC_MAIL_SERVER_KEY = Service_ID
NEXT_PUBLIC_MAIL_PRIVATE_KEY = public key
  1. 라이브러리 다운로드 및 선언

터미널에 install을 해주고나서 npm install @emailjs/browser --save

사용하고자 하는 파일에 import해주면 된다. import emailjs from "@emailjs/browser";

  1. env 파일을 사용할 수 있도록 선언해준다.
 const NEXT_PUBLIC_MAIL_TEMPLATE_KEY =
    process.env.NEXT_PUBLIC_MAIL_TEMPLATE_KEY;
  const NEXT_PUBLIC_NEXT_PUBLIC_MAIL_SERVER_KEY =
    process.env.NEXT_PUBLIC_NEXT_PUBLIC_MAIL_SERVER_KEY;
  const NEXT_PUBLIC_MAIL_PRIVATE_KEY = process.env.NEXT_PUBLIC_MAIL_PRIVATE_KEY;

  1. 이벤트를 함수로 처리해준다.

나는 타입스크립트를 이용하고 있기 때문에 조금 더 처리가 필요했다.

  • form.current가 null일 경우, 경고창을 따로 띄워주며 sendForm 함수를 이용할 때 오류를 없애주었다.
const onSubmitForm = (event: { preventDefault: () => void }) => {
    event.preventDefault();

    if (form.current == null) {
      alert("폼이 존재하지 않습니다.");
      return;
    }

    try {
      emailjs.sendForm(
        NEXT_PUBLIC_NEXT_PUBLIC_MAIL_SERVER_KEY as string,
        NEXT_PUBLIC_MAIL_TEMPLATE_KEY as string,
        form.current,
        NEXT_PUBLIC_MAIL_PRIVATE_KEY
      );
      alert("메일이 전송되었습니다.");
      setInputName("");  // 여기에 있는 3개의 set함수는 메일 전송이 이루어진 이후 input 창의 내용을 지우기 위한 후처리이다.
      setInputEmail("");
      setMessage("");
    } catch (error) {
      alert("메일 전송에 실패하였습니다. ");
    }
  };
  1. input 태그에 name을 설정해준다.

아까 emailJS의 템플릿에서 본 것처럼 안에 있는 이름으로 사용해야한다.

전체 코드

import { useRef, useState } from "react";
import emailjs from "@emailjs/browser";

export default function Contact() {
  const NEXT_PUBLIC_MAIL_TEMPLATE_KEY =
    process.env.NEXT_PUBLIC_MAIL_TEMPLATE_KEY;
  const NEXT_PUBLIC_NEXT_PUBLIC_MAIL_SERVER_KEY =
    process.env.NEXT_PUBLIC_NEXT_PUBLIC_MAIL_SERVER_KEY;
  const NEXT_PUBLIC_MAIL_PRIVATE_KEY = process.env.NEXT_PUBLIC_MAIL_PRIVATE_KEY;

  const form = useRef<HTMLFormElement>(null);
  const [inputName, setInputName] = useState<string>("");
  const [inputEmail, setInputEmail] = useState<string>("");
  const [message, setMessage] = useState<string>("");

  const onSubmitForm = (event: { preventDefault: () => void }) => {
    event.preventDefault();

    if (form.current == null) {
      alert("폼이 존재하지 않습니다.");
      return;
    }

    try {
      emailjs.sendForm(
        NEXT_PUBLIC_NEXT_PUBLIC_MAIL_SERVER_KEY as string,
        NEXT_PUBLIC_MAIL_TEMPLATE_KEY as string,
        form.current,
        NEXT_PUBLIC_MAIL_PRIVATE_KEY
      );
      alert("메일이 전송되었습니다.");
      setInputName("");
      setInputEmail("");
      setMessage("");
    } catch (error) {
      alert("메일 전송에 실패하였습니다. ");
    }
  };

  return (
    <>
      <section className="text-gray-600 body-font relative">
        <div className="container px-5 py-24 mx-auto">
          <div className="flex flex-col text-center w-full mb-12">
            <h1 className="sm:text-3xl text-2xl font-medium title-font mb-4 text-gray-900">
              CONTACT ME
            </h1>
            <p className="lg:w-2/3 mx-auto leading-relaxed text-base">메시지</p>
          </div>
          <form ref={form} onSubmit={onSubmitForm}>
            <div className="lg:w-1/2 md:w-2/3 mx-auto">
              <div className="flex flex-wrap -m-2">
                <div className="p-2 w-1/2">
                  <div className="relative">
                    <label
                      htmlFor="name"
                      className="leading-7 text-sm text-gray-600"
                    >
                      Name
                    </label>
                    <input
                      type="text"
                      name="from_name"
                      className="w-full bg-gray-100 bg-opacity-50 rounded border border-gray-300 focus:border-indigo-500 focus:bg-white focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
                      required
                      value={inputName}
                      onChange={(e) => setInputName(e.target.value)}
                    />
                  </div>
                </div>
                <div className="p-2 w-1/2">
                  <div className="relative">
                    <label
                      htmlFor="email"
                      className="leading-7 text-sm text-gray-600"
                    >
                      Email
                    </label>
                    <input
                      type="email"
                      name="reply_to"
                      required
                      className="w-full bg-gray-100 bg-opacity-50 rounded border border-gray-300 focus:border-indigo-500 focus:bg-white focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
                      value={inputEmail}
                      onChange={(e) => setInputEmail(e.target.value)}
                    />
                  </div>
                </div>
                <div className="p-2 w-full">
                  <div className="relative">
                    <label
                      htmlFor="message"
                      className="leading-7 text-sm text-gray-600"
                    >
                      Message
                    </label>
                    <textarea
                      name="message"
                      className="w-full bg-gray-100 bg-opacity-50 rounded border border-gray-300 focus:border-indigo-500 focus:bg-white focus:ring-2 focus:ring-indigo-200 h-32 text-base outline-none text-gray-700 py-1 px-3 resize-none leading-6 transition-colors duration-200 ease-in-out"
                      value={message}
                      onChange={(e) => setMessage(e.target.value)}
                    ></textarea>
                  </div>
                </div>
                <div className="p-2 w-full">
                  <button className="flex mx-auto text-white bg-indigo-500 border-0 py-2 px-8 focus:outline-none hover:bg-indigo-600 rounded text-lg">
                    <input type="submit" value="SEND" required />
                  </button>
                </div>
                <div className="p-2 w-full pt-8 mt-8 border-t border-gray-200 text-center">
                  <a className="text-indigo-500">sonrisa-bonita@naver.com</a>
                  <p className="leading-normal my-5">
                    경기도 용인시 기흥구 영덕동
                  </p>
                  <span className="inline-flex">
                    <a className="ml-4 text-gray-500">
                      <svg
                        fill="none"
                        stroke="currentColor"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                        stroke-width="2"
                        className="w-5 h-5"
                        viewBox="0 0 24 24"
                      >
                        <rect
                          width="20"
                          height="20"
                          x="2"
                          y="2"
                          rx="5"
                          ry="5"
                        ></rect>
                        <path d="M16 11.37A4 4 0 1112.63 8 4 4 0 0116 11.37zm1.5-4.87h.01"></path>
                      </svg>
                    </a>
                    <a className="ml-4 text-gray-500">
                      <svg
                        fill="currentColor"
                        stroke-linecap="round"
                        stroke-linejoin="round"
                        stroke-width="2"
                        className="w-5 h-5"
                        viewBox="0 0 24 24"
                      >
                        <path d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z"></path>
                      </svg>
                    </a>
                  </span>
                </div>
              </div>
            </div>
          </form>
        </div>
      </section>
    </>
  );
}

나는 tailwind block css를 이용하였다. 코드가 상당히 지저분해보인다는 단점을 가지고 있는 tailwindCSS… 이게 개인용 프로젝트이고 개인용 포트폴리오라 가능한 일인 것 같다.

vercel 배포시,

vercel 배포를 하면, local에서 작동이 잘되던 메일 전송이 안되는 걸 확인할 수 있다.

그건 바로 .env 파일을 보통 github에 올라가지 않도록 gitignore로 막아두기 때문인데,

이를 해결하기 위해서는 vercel에 프로젝트 안에 들어가서 settings 메뉴의 Environment Variables에서 아까 .env에 지정한 key와 value값 3개를 넣고 save해주면 된다!

image