Skip to content

Releases: Choi-HyunHo/blog

v1.6.2 convert tag value to param value

21 Jun 13:30
Compare
Choose a tag to compare

소개

기존의 포스팅들을 tag 별로 구분하여 해당 tag 에 대한 포스팅만 보여질 수 있는 기능이었습니다.

문제점

하지만 포스팅을 누르고 사용자가 뒤로가기를 했을 때 tag 값이 유지가 안되고 스크롤 위치는 기억하여 정상적으로 유지되나
사용자가 선택한 tag가 아닌 무조건 'All' 태그인 경우로 이동하여 선택한 tag가 유지가 안되는 문제점이 있었습니다.

  • 이는 사용자가 글을 보기전 마지막으로 접한 페이지와 달라지고 UX가 좋지 않았습니다.
  • 해당 tag의 다른 글을 보고싶으나 스크롤을 맨위로 올려야 하는 번거로움이 생깁니다.

해결방안

tag 값을 해당 컴포넌트에서 사용하는 것이 아니라 param 으로 넘겨서 해당 param 에 맞는 포스트만 보여주게 된다면
글을 본 이후 다시 뒤로가기를 해도 마지막으로 사용자가 본 페이지가 정상적으로 유지됩니다.

1. [tag] 폴더를 만들어서 param 값을 받을 수 있게 페이지를 추가했습니다.

image

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 기능 추가

15 Jun 16:01
Compare
Choose a tag to compare

소개

기존 첫 메인 화면에서 보여지는 포스트는 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" // ✅  똑같이 사용
---

적용 화면

image

v1.4.2 Contact me 메일 전송 기능 추가

11 Jun 07:39
Compare
Choose a tag to compare

v1.4.2 Contact me 메일 전송 기능 추가

소개

/contact 페이지에서 이메일을 통해 연락을 받을 수 있도록 기능을 추가했습니다.

라이브러리

  • yup
  • nodemailer

가능한 이유

Next.js 는 풀스택 프레임워크 입니다.

  • Next.js를 사용하면 서버측 로직을 처리하고 HTTP 요청에 응답할 수 있는 서버리스 함수인 API 경로를 정의 할 수 있습니다.
  • 공식문서
  1. 메일 요청은 client → server 로 요청
  2. server 에 등록된 API Route 를 이용 → node 환경에서 동작하는 라이브러리 이용 가능
  3. 처리 결과를 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

1

그리고 이메일 계정과 해당 모자이크 된 패스워드를 .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

08 Jun 16:14
Compare
Choose a tag to compare

메타데이터 등 전반적인 기반 완성