Releases: Choi-HyunHo/blog
Releases · Choi-HyunHo/blog
v1.6.2 convert tag value to param value
소개
기존의 포스팅들을 tag 별로 구분하여 해당 tag 에 대한 포스팅만 보여질 수 있는 기능이었습니다.
문제점
하지만 포스팅을 누르고 사용자가 뒤로가기를 했을 때 tag 값이 유지가 안되고 스크롤 위치는 기억하여 정상적으로 유지되나
사용자가 선택한 tag가 아닌 무조건 'All' 태그인 경우로 이동하여 선택한 tag가 유지가 안되는 문제점이 있었습니다.
- 이는 사용자가 글을 보기전 마지막으로 접한 페이지와 달라지고 UX가 좋지 않았습니다.
- 해당 tag의 다른 글을 보고싶으나 스크롤을 맨위로 올려야 하는 번거로움이 생깁니다.
해결방안
tag 값을 해당 컴포넌트에서 사용하는 것이 아니라 param
으로 넘겨서 해당 param
에 맞는 포스트만 보여주게 된다면
글을 본 이후 다시 뒤로가기를 해도 마지막으로 사용자가 본 페이지가 정상적으로 유지됩니다.
1. [tag] 폴더를 만들어서 param 값을 받을 수 있게 페이지를 추가했습니다.
2. post 값을 불러와서 params 으로 받은 값과 같은지 비교해서 걸러냅니다.
export default async function postPage({ params }: Props) {
const data = await getPosts();
const filtered = data.filter((data: any) => data.tag === params.tag);
return (
<>
<TagView filtered={filtered} pData={params.tag} />
</>
);
}
pData
라는 params 로 받은 tag 값을 props 로 전달하여 기존의 tag에 쓰이던 값들을 대체했습니다.
v1.5.2 Featured 기능 추가
소개
기존 첫 메인 화면에서 보여지는 포스트는 Recent 뿐이었는데 내가 쓴 글 목록 중에 입맛에 골라서 화면에 고정 시킬 수 있는 기능을 추가 했습니다.
추가 사항
contentlayer.config.ts
import { defineDocumentType, makeSource } from "contentlayer/source-files";
import rehypePrettyCode from "rehype-pretty-code";
const options = {
theme: "github-dark",
};
export const Post = defineDocumentType(() => ({
name: "Post",
contentType: "mdx",
filePathPattern: `**/*.mdx`,
fields: {
title: { type: "string", required: true },
date: { type: "string", required: true },
description: { type: "string", required: true },
tag: { type: "string", required: true },
feature: { type: "string" }, // ✅ 추가 : 필수는 아니라 필요한 포스트에만 사용하면 됩니다.
},
}));
export default makeSource({
contentDirPath: "posts",
documentTypes: [Post],
mdx: {
rehypePlugins: [[rehypePrettyCode, options]],
},
});
신규 컴포넌트
FeaturePost.tsx
import React from "react";
import { posts } from "@/service/posts";
import BlogPost from "./BlogPost";
export default function FeaturePost() {
const featurePosts = posts.filter((p) => p.feature === "true");
return (
<div className={`flex flex-col`}>
{featurePosts.map((post) => (
<BlogPost
date={post.date || ""}
title={post.title || ""}
des={post.description || ""}
slug={post._raw.flattenedPath || ""}
tag={post.tag || ""}
key={post._id}
/>
))}
</div>
);
}
Featured 글 예시
---
title: 나의 첫 번째 퇴사 회고
date: "2023-06-09"
description: 1st. company bye 🥹
tag: Review
feature: "true" // ✅ 똑같이 사용
---
적용 화면

v1.4.2 Contact me 메일 전송 기능 추가
v1.4.2 Contact me 메일 전송 기능 추가
소개
/contact 페이지에서 이메일을 통해 연락을 받을 수 있도록 기능을 추가했습니다.
라이브러리
- yup
- nodemailer
가능한 이유
Next.js 는 풀스택 프레임워크 입니다.
- Next.js를 사용하면 서버측 로직을 처리하고 HTTP 요청에 응답할 수 있는 서버리스 함수인 API 경로를 정의 할 수 있습니다.
- 공식문서
- 메일 요청은 client → server 로 요청
- server 에 등록된 API Route 를 이용 → node 환경에서 동작하는 라이브러리 이용 가능
- 처리 결과를 client 로 반환
추가 사항
app 폴더안에 api 폴더를 만들고 최종적으로 route.ts 파일을 만들면 API 호출이 가능합니다.
service/email.ts
export async function sendContactEmail(email: {
email: string;
subject: string;
message: string;
}) {
const res = await fetch("/api/contact", { // ✅
method: "POST",
body: JSON.stringify(email),
headers: {
"Content-Type": "application/json",
},
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.message || "Server request failed ❌");
}
return data;
}
nodemail.ts
- nodemailer 라이브러리 코드
import nodemailer from "nodemailer";
import { EmailData } from "@/app/api/contact/route";
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
user: process.env.NEXT_PUBLIC_NODEMAILER_USER,
pass: process.env.NEXT_PUBLIC_NODEMAILER_PASS,
},
});
export async function mailInfo({ email, subject, message }: EmailData) {
const data = {
to: process.env.NEXT_PUBLIC_NODEMAILER_USER,
subject: `[Blog] ${subject}`,
html: `
<h1>${subject}</h1>
<p>${message}</p>
<br/>
<p>From. ${email}</p>
`,
};
return transporter.sendMail(data);
}
env 경로는 사용자에게 메일을 받을 계정을 등록하면 됩니다.
계정 추가 방법(feat.google)
구글계정관리 -> 보안 -> 2단계인증 -> app password

그리고 이메일 계정과 해당 모자이크 된 패스워드를 .env 폴더에 등록하면 됩니다.
- 추가로 이미 배포를 했다면 환경 변수 등록을 해야 합니다.
app/api/contact/route.ts
import * as yup from "yup";
import { mailInfo } from "@/service/nodemail";
export interface EmailData {
email: string;
subject: string;
message: string;
}
const vaildSchema = yup.object().shape({
email: yup.string().email().required(),
subject: yup.string().required(),
message: yup.string().required(),
});
export async function POST(req: Request) {
const body = await req.json();
if (!vaildSchema.isValidSync(body))
return new Response("유효하지 않습니다", { status: 400 });
return mailInfo(body)
.then(
() =>
new Response(JSON.stringify({ message: "메일 전송 성공" }), {
status: 200,
})
)
.catch((error) => {
console.error(error);
return new Response(JSON.stringify({ message: "메일 전송 실패" }), {
status: 500,
});
});
}
v1.3.2
메타데이터 등 전반적인 기반 완성