[회고] NextJs,Tailwind를 사용하여 프로젝트 작업

ohoho·2025년 6월 17일
0

React

목록 보기
14/15

NextJs를 사용해서 처음으로 실무에서 혼자 모든걸 선택해서 간단한 싱글페이지를 만들었다.
사용한 프레임워크와 라이브러리는 NextJs, Tailwind, Mui, Framer Motion

폴더구조

보안상 주요기능만 추출

//싱글페이지이기 때문에 `(main)`안에 컨텐츠별 컴포넌트를 넣었고, 최상위 `page.tsx`에 삽입하여주었다
app/
├── (main)/
│   ├── Contact.tsx
│   ├── Header.tsx
│   └── Main.tsx
├── api/
│   └── news/
│       └── route.ts
├── layout.tsx
├── page.tsx
└── global.css

//Components안에는 재사용 또는 반복사용하는 컴포넌트들을 넣어주었다
components/
├── Button.tsx
├── Footer.tsx
├── FormField.tsx
└── Inquiry.tsx

//재사용 가능한 hook은 hook.ts에 작성
//mui를 커스텀해서 사용하기에 Provider설정
lib/
├── hooks.ts
└── providers/
    └── MuiThemeProvider.tsx

middleware.ts
theme.ts

주요기능

1. 네이버 뉴스 API

  • 특정 키워드의 뉴스만 받아오기 위해 네이버에서 Client Id, secret Key값을 발급 받은 후 api/news/route.ts폴더 안에 Get함수를만들어서 받아온다.
    (id값과 key값은 env파일에 저장해서 관리)
"use server"
import { NextResponse } from "next/server";

export async function GET() {
  const query = process.env.NEWS_KEYWORD!
  //쿼리 문구에 띄어쓰기나 특수문자를 제거하기 위해 인코딩 사용
  const url = `https://openapi.naver.com/v1/search/news.json?query=${encodeURIComponent(query)}&display=10&start=1&sort=date`;

  //헤더에 id,key값 담기
  const response = await fetch(url, {
    headers: {
      "X-Naver-Client-Id": process.env.NAVER_CLIENT_ID!,
      "X-Naver-Client-Secret": process.env.NAVER_CLIENT_SECRET!,
    },
  });

  const data = await response.json();
  //클라이언트에게 응답값 보내기 위해 NextResponse에 담기
  return NextResponse.json(data);
}

//실제 사용할 컴포넌트
useEffect(() => {
    const getNews = async () => {
      //위에서 만들어둔 route.ts의 Get함수 실행 요청
      const res = await fetch("/api/news");
      //json파싱
      const data = await res.json();
      //useState에 담기
      setNewsItem(data.items);
    };
  //API 라우트 호출
    getNews();
  }, []);

API의 route.ts에는 Get, Post, Put, Delete를 작성할 수 있고 한개의 파일에 모두 작성하여 관리한다.

fetch("/api/news", { method: "GET" }) → GET() 실행
fetch("/api/news", { method: "POST", body: ... }) → POST() 실행
fetch("/api/news", { method: "DELETE" }) → DELETE() 실행

2. 공통 hooks

  • 팝업 밖 클릭시 팝업이 닫히는 효과 주기 위해 공통 hook으로 사용
"use client"
import { useRef, useEffect, RefObject } from 'react';

export function useClickOutside<T extends HTMLElement>(
  callback: () => void
): RefObject<T | null> {
  const ref = useRef<T>(null);
  //callback을 사용해서 사용할때 닫힐 함수 넣어줌
  useEffect(() => {
    const onClickOutside = (event: MouseEvent) => {
      if (ref.current && !ref.current.contains(event.target as Node)) {
        callback();
      }
    };

    document.addEventListener('mousedown', onClickOutside);
    //언마운트될때 제거
    return () => {
      document.removeEventListener('mousedown', onClickOutside);
    };
  }, [callback]);

  return ref;
}

//실제 사용할 컴포넌트
const popupRef = useClickOutside<HTMLDivElement>(() => {
  setIsActive(pre => !pre)
});

<div ref={popupRef}>팝업</div>

3. 미들웨어

  • 특정 파라미터로 들어오는 uri 경로 리다이렉트 하기위해 사용
import { NextRequest, NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
    const { pathname, searchParams } = request.nextUrl;
    if (pathname === '/aa') {
        const data = searchParams.get('data');
        if (data) {
        try {
          // 인코딩 되어 있는 경로를 디코딩 ( %&*# > params=1)하고 문자열로 반환
            const decoded = Buffer.from(decodeURIComponent(data), 'base64').toString();
          // 문자열 파싱
            const params = new URLSearchParams(decoded);
            const aaParam = params.get('aaParam');
              if (aaParam === 'aaa') {
                // 조건맞으면 원하는 경로로 이동
                const redirectUrl = new URL('/', request.url);
                return NextResponse.redirect(redirectUrl);
              }
          	// 조건 안맞을때 특정 경로로 이동
            return NextResponse.redirect(new URL('/', request.url));
        } catch (error) {
            console.error('디코딩 오류:', error);
            return NextResponse.redirect(new URL('/error', request.url));
        }
        }
    }
    return NextResponse.next();
}

export const config = {
  // /aa/경로로 들어오는것에만 로직 검증
    matcher: '/aa/:path*'
};

정리

실제 프로젝트에서 혼자 처음부터 끝까지 어떤 라이브러리를 사용할지 생각하며 작업을 해봤는데
혼자 정하다 보니 뭐를 골라 사용해야할지 선택지가 많아 어려웠고 처음에 사용하려고 설치를 하였다가 나중에 사용안한것들도 있다.


그 중 선택하기 어려웠거나 사용하기 까다로웠던것들을 추려보자면,

- css

이번에 만든 페이지는 싱글페이지라 tailwind를 사용했지만 생각보다 코드가 길어져서 추후 대형프로젝트를 진행한다면 styled-component를 섞어서 사용하는게 좋을거 같음
그래도 tailwind로 공통 theme색상이나 반응형 분기처리 하기는 좋아서 앞으로도 섞어서 사용해야할거같음

- Mui

처음 기획에서 디자인이 단순하다 하여 mui를 사용하려고 설치해서 input, Accodian에 사용을 하였는데 추후 디자인이 많이 변경되어서 mui를 사용한것들을 디자인 커스텀 해서 사용하게 되었다.
커스텀이 단순하게 작성할줄 알았는데 provider를 따로 만들어 코드에 넣어주고 theme.ts에 커스텀한 코드를 넣는게 꽤나 복잡하고 처음 커스텀을 해보는거라 어려웠음

- Framer Motion

이건 사용하기 잘했다고 생각들음
처음엔 단순 css로 애니메이션 작업을 하였는데 코드도 길어지고 반응형으로 바뀌면서 에러나는것도 있어서 framer motion으로 바꿨더니 틀어지는것도 없고 코드도 간결해지고 매우 만족

- Swiper

슬라이드를 사용하기위해 설치했다가 pc랑 mobile코드를 어떻게 할까 고민하던중 Swiper 옵션 내에 breakePoint라는 기능이 있어서 사용했는데 매우 만족


이번에 이렇게 작업을 해보면서 오랜만에 framer motion이나 middleware를 다시 쓸 수 있어서 복습을 하는 느낌이었고,

실제 작업을 하면서 pc와 mobile이 분리되는 반응형 작업까지 같이 하였는데 컴포넌트 내에서 반응형을 어떻게 나눠야할까 고민을 많이 해보다가 이것저것 사용하였는데
최종적으로 사용한것은 tailwind의 hidden lg:block 코드를 많이 사용했고,

옵션 내에서 사용하거나 특정 기능에서 사용해야할때는 mui에서 제공하는 기능을 사용해 const isMobile = useMediaQuery("(max-width: 1023px)") 작업을 마무리했다

아직까지 아쉬운점은 반응형을 저렇게 나눈게 베스트가 맞나라는 의문이 들어서 저부분에 대해 어떤게 제일 좋은 방법이었을까 더 생각해봐야겠다

0개의 댓글