JamStack을 통해 ‘상품목록 사이트’ 만들어보기
1. 구성 및 기획단계
a. 프로젝트 목적
- ‘필요한 것을 개발한다’를 실행할 수 있는 좋은 기회
- 아버지가 퇴직 후 최근 농산물을 도매로 판매하는 식자재마트를 개업하셨다. 아직 규모가 작기 때문에 모든 주문을 전화로 직접 받고 계신데, 취급하는 상품목록을 일일히 전화로 대답하고 있는 상황이었다. 이에 따라 현재 취급 중인 상품목록을 보여주는 사이트를 개발하고자 한다.
- 추가로 변경되는 상품목록 리스트의 손쉬운 변경 및 인쇄 기능으로 편의를 높여드리고자 한다.
- JAMStack(Next + Strapi) 에 대한 공부 및 실전연습
- 간단한 규모의 웹사이트이기 때문에, 새로운 학습과 병행을 하고 싶은 욕심이 있었다.
- + Next의 정적 페이지 생성기로서의 사용과 함께 브라우저 렌더링 관점에서도 공부할 기회라고 생각하였다.
JAMStack은 가볍고 빠르게 필요한 앱을 만들기 위한 접근방식으로 알고있었고, 이 기회를 통해 이후 생각나는 아이디어나 문제들을 빠르게 실행할 수 있는 기반을 마련하고 싶었다.
b. 스킬셋 구성
[Front]
- Next.js (SSG를 위해 사용)
- Recoil (프론트엔드 상태관리 필요시 사용예정)
- Emotion (Styled-Component)
c. 요구사항
현재 상황에서의 서비스 요구사항은 크게 2가지이다.
- 기존 고객들이 전화를 통해 확인했던 판매목록을 ⇒ 웹사이트를 통해 판매목록과 가격을 확인하는 것.
- 기존 가게에 게시하는 가격표를 한글(Hwp)로 입/출력 ⇒ 웹사이트 내에서 판매목록/가격을 수정 및 출력
추후에는 웹사이트를 통한 주문접수를 고려하고 있지만, 당장 오픈한지 얼마되지 않는 상황에서 필요한 부분에서의 최소한의 변화만을 원하셨다.
- 따라서, 개발적으로는 1) 상품 목록의 CRUD 2) 가격표 수정 및 출력 라는 2가지 메인 feature를 산출하였다.
d. 웹사이트 구조

페이지는 총 2개가 존재한다.
Home페이지에서 일반 고객들이 상품목록과 가격표를 확인할 수 있도록하였다.
Admin 페이지는 아버지 및 관리자만 상품목록 CRUD에 접근할 수 있도록 처리하기로 결정하였다.
[Home]

Home 페이지의 NavBar에서 상품목록과 가격표를 목적에 맞게 선택하도록 구성하였다.
상품목록은 카테고리별 탭을 별도로 두어, 기본적인 필터링이 가능하도록 하였다.
(
곡류 / 소스류 / 채소 / 가공
)상품카드의 경우 ‘이미지 / 이름 / 가격’ 정도록 심플하게 보여줄 예정이다.
[Admin]
Admin페이지는 ‘왼쪽의 메뉴탭’, ‘상단의 액션영역’ 그리고 ‘뷰 영역’ 으로 레이아웃을 구성하고자 한다.
상품의 CRUD를 할 수 있는 상품관리 탭, 물품과 가격만을 표형식으로 제공하는 가격표 탭으로 구성된다.


개발기간이 3일로 짧은 관계로 이 정도 레이아웃(와이어프레임 구성)으로 마치고, 본격적인 환경세팅과 개발을 진행해보고자 한다.
2. 개발환경 세팅
a. 모노레포 환경 구성
[Why]
지금까지 프로젝트를 진행할 때, 프론트와 서버가 서로 다른 git레포에서 관리를 해왔다.
규모가 작았고, 명확히 역할이 구분되어 있었기 때문에 두 레포간의 공유 자원의 문제는 경험하지 못하였지만, repository위치 자체가 다름으로 인해 관리의 어려움을 겪었다.
- issue 및 코드리뷰의 공유, branch rule과 dependency 관리 등..
그래서 이번에는 1인 프로젝트임에도 불구하고, issue관리 및 개발 편의성을 위해 모노레포(monorepo)환경을 구성해보고자 하였다.
[What]
모노레포를 구성하는 여러가지 방법이 존재했지만, 가장 간단한 yarn workspace를 통한 패키지 관리 방법을 선택하였다.
yarn berry를 이용하여 zero-install 방식을 고려해보았지만, 패키지 자체가 매우 작은 상황에서 큰 실효성을 얻을 수 없다는 판단 아래 추후 사용을 위한 학습정도로만 진행하였다.
[How]

yarn workspace는 루트경로의 packge.json에서 하위 폴더의 package들을 한 번에 설치할 수 있도록 도와준다.
설치과정에서 하위폴더가 가지고 있는 package들의 중복을 제거하고, 루트경로의 node_modules로 호이스팅하여 1번의 설치를 진행한다.
실제 사용을 위한 코드는 아주 간단하다.
루트경로의 package.json에서 다음과 같이 설정한다.
{ ... //workspaces자체를 NPM에 publish하는 것을 막아주는 옵션 "private": true "workspaces": ["client", "server"] }
개별의존성 설치가 필요한 경우에는 다음과 같이 등록할 수 있다.
yarn workspace 워크스페이스명 add 패키지명 [--dev]
[에러로그]
미해결
ESLint 미적용 에러
b. Strapi로 백엔드 환경 구성 (Headless CMS)
[Why]
Headless CMS로 Strapi를 선택한 것은 이전 프로젝트의 좋은 사용 경험 때문이었다.
GUI를 통한 모델의 손쉬운 생성 및 관리 API endpoint 제공과 플러그인으로 개발 편리성 획득이 주요 이유였고, 복잡한 쿼리등이 필요할 때에는 직접 서버 코드를 customize하게 변경할 수 있었기 때문에 선택하지 않을 이유가 없었다.
또 당시에는 v3 → v4로 출시 직후라 migration이 덜 된 부분이 있어 곤란함을 느꼈지만, 현재는 v4 기준 공식문서도 굉장히 알차게 작성되어 있어 참고하기 편리하였다.
- 1) CRA와 같은 템플릿 환경으로 빠른 시작

- 2) GUI를 통한 모델과 인스턴스의 CRUD
가장 큰 장점은 역시 admin 패널을 제공하여, GUI 환경에서 데이터 모델들을 정의하고 관리할 수 있다는 것이다.
Strapi admin패널에서는 Content-type Builder 를 통해 데이터구조를 손쉽게 생성할 수 있다.
각 모델이 가지는 filed의 type과 relation 설정도 지원하기 때문에, 매우 빠르게 데이터 구조를 만들 수 있다.


- 3) 모델에 대한 CRUD에 관한 API endpoint의 자동 제공
- Strapi에서는 모델을 생성하게 되면, 기본적으로 CRUD에 관한 REST API endpoints를 자동으로 제공한다.
- 기본 API의 제공은 빠르게 프론트 작업에 집중할 수 있도록 하여, 개발속도 향상에 엄청난 도움을 주었다.

- 4) 효과적인 Plugin의 제공
- Strapi를 매뉴얼대로 설치하면 기본적으로 설치되는 Plugin들이 존재한다.
- Users & Permissions 플러그인은 특정 API를 특정 사용자에게만 오픈할 수 있도록 한다.
- 이러한 권한 관리 등을 GUI 환경에서 손쉽게 할 수 있다는 것은 보안이나 인증관련 신경써야할 부분을 줄여주어 큰 도움이 되었다.
[How]
이 모든 것들을 하기 위해 필요한 코드는 1줄이다!
- 1) 설치
yarn create strapi-app [app 이름] --quickstart
설치 이후 admin 패널에서 GUI를 통한 데이터의 변경은 저장 시 코드 상으로 반영된다.
- 2) 모델 생성
- “Content-Type Builder” 탭을 통해 Model의 이름을 작성 후, field를 type에 맞게 설정 후 추가하여, 모델을 완성한다.

- 3) 데이터 등록
- “Content Manager” 탭을 통해 만들었던 모델의 인스턴스(데이터)를 손쉽게 생성/관리 할 수 있다.


c. Next.js로 프론트 환경 구성 (MarkUp)
[Why]
JAMStack의 M(MarkUp)을 담당하는 SSG(Static Site Generator)로 선택할 수 있는 스택은 Gatsby와 NextJS 가 있었다.
- 두 개의 프레임워크를 사용해본 경험이 없었기 때문에, 학습의 측면에서는 어떤 것을 사용하여도 상관 없다고 생각했다.
- 그래도 두 프레임워크 중 선택의 기준이 필요하였고, 두 프레임워크의 가장 큰 차이점을 찾아보았다.
- Gastsby의 경우 플러그인을 통한 매직이 가능하다는 장점이 있는 반면, 데이터 변경의 경우 매번 반영하기 위해 새로 빌드 및 배포를 진행(혹은 CSR 방식의 API 호출)하는 단점이 존재했다.
- NextJS의 경우 Gastyby 플러그인 같은 매직을 제공하지는 않지만, 데이터 변경에 따른 변화를 SSR의 도움을 받아 처리할 수 있다는 장점이 있었다.
- 나는 GatsbyJS를 사용했을 때 얻을 수 있는 플러그인의 편리성과 완전한 SSG방식으로 인한 렌더링 속도의 강점 보다는, Next.js를 학습하면서 사용하고 싶은 욕심이 었었고, 상품 목록의 변경등의 데이터 변경이 발생할 부분과 추후 e-commerse로의 확장 가능성을 고려했을 때 SSR 방식도 같이 사용할 수 있는 NextJS를 선택하기로 결정하였다.
[How]
- 설치
npx create-next-app [app 이름]
React에 CRA가 있다면 Next에서는 CNA를 통해 기본 패키지들이 설치되어 있는 환경으로 빠르게 시작할 수 있다.
d. 배포환경 구성 With Vercel, Heroku
[why]
본격적인 개발에 앞서 배포환경을 먼저 구축하고자 하였다.
모노레포 및 Next 사용이 처음이었기 때문에 최대한 돌발 변수를 줄여 시간적으로 빼앗기지 않기 위해서이다.
- 클라이언트(
Next
)는 Vercel을 통한 배포 - AWS Amplify도 잠깐 고려했지만, Next를 만든 회사가 Vercel이기에 추가적으로 이점을 바라며 선택하였다.
- 서버(
Strapi
)는 Heroku를 통한 배포 - 무료이기 때문에 선택
- but 22.12월 부터 무료 정책이 끝나며 이후 digitalOcean등의 무료 배포 사이트 고려 중
[How]
요즘은 배포를 위한 서비스들이 Git 연동과 GUI 환경을 너무나 잘 지원해주기 때문에 큰 어려움 없이 구축할 수 있었다.
- Vercel 세팅
- Git 연동
- monoRepo이기 때문에 Root Directory를
/client
로 설정 - 환경변수 및 Production 브랜치 설정
- Heroku 세팅
- Strapi는 기본적으로 PostgreSQL을 사용하기 때문에, Heroku에서 DB 사용 변경을 위한 세팅을 진행해주어야 한다.
- Strapi 홈페이지에 해당 과정이 자세히 나와있기 때문에, 설명은 생략한다.
- https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/deployment/hosting-guides/heroku.html#_8-update-yarn-lockfile
- Client-Server 연결
- Client가 Server를 바라 볼 수 있도록 각각 환경변수로 추가해준다.
- Vercel에서 ‘
NEXT_PUBLIC_STRAPI_API_URL
=strapi의 url
'
[배포자동화]
develop
브랜치에 새로운 코드가 반영될 경우, client와 server 모두 새로 build & 배포를 자동으로 진행하도록 설정하고자 하였다.- Vercel
- Production Branch 자체를
develop
브랜치로 변경해주어, push된 commit을 추적/자동배포 할 수 있도록 설정
- Heroku
- 마찬가지로 Automatic deploys 탭에서
develop
브랜치에 대한 자동배포를 on으로 설정한다.
e. 빌드 자동화 With vercel deploy hooks
JAMStack의 핵심은 앱자체가 정적페이지들로 구성되어 있다는 것이다.
화면에 보여줄 데이터의 변경이 잦지는 않지만, 아예 없는 상황은 아니다.
상품목록을 추가/삭제 동작이 발생할 때 마다 매번 수동으로 배포를 진행해줄 수는 없었다.
이를 지원하는 기능이 반드시 있을 것이라고 생각하고 찾아본 결과, Vervel에서 특정 상황에서의 배포를 위한 트리거 Hook을 제공하고 있었다.
- Vercel
Deploy Hooks
특정 브랜치를 배포시키도록 하는 url 제공

- Strapi admin
Webhooks
특정 모델에서 CRUD가 발생할 경우, url 형식의 트리거를 실행시킴

3. 상품 목록 페이지 구현
a. 마크업 만들기
실제 개발에 들어가기 앞서, 만들어두었던 와이어프레임을
html+css
마크업으로 변환하는 작업을 진행하였다.와이어프레임을 보면서 기능구현과 CSS 작업을 동시에 하다보면 집중이 분산되는 경우가 많았다. 여러 과제테스트를 진행할 때, 마크 업이 주어진 상태에서 온전히 기능구현에 집중할 수 있다는 것을 느꼈기 때문에 미리 기능 구현 전 마크업을 구성하였다.
[After - Html 마크업]
Html+css
with 더미데이터™¡

b. SEO를 위한 컴포넌트 만들기
Next.js 그리고 정적 페이지들의 가장 큰 장점은 build 시 각 페이지에 맞는 meta 태그를 설정할 수 있어 SEO를 고려할 수 있다는 것이다.
Client 코드 내부에서 각 페이지에 대한 meta태그를 미리 설정할 수도 있지만,
페이지에 대한 meta 태그 정보들을 Strapi Admin에서 관리하도록 하여, 쉽게 meta 태그의 변경이 가능하도록 하였다.
- Strapi
- SingleType으로 각 페이지에 대한 meta 정보를 저장하도록 모델 생성
- global에는
favicon
,웹 title 정보
추가 - 각 페이지에는 필요한
metaTag
정보들을 정의


- SEO 컴포넌트 (
Next
) getStaticProps
에서 html 파일 생성 전에, Strapi Admin에서 생성한 페이지별 meta태그 데이터들을 불러와서 props로 넘겨준다.<Seo>
컴포넌트는 meta 태그 데이터를 prop으로 받아 next에서 제공하는<Head>
컴포넌트안에서 공통적으로 처리될 수 있도록 하는 역할을 한다.
- 기타 세팅
- Image Domain 설정
- global style 지정 with Emotion
c. 컴포넌트 구현
- [Nav 컴포넌트]
/products
,/price
,/admin
point
추가/삭제/변경 시 유연하게 대응할 수 있도록,navItems
를 객체로 만들어 관리고민
[1. 삼항연산자의 클린한 사용법 고민]
요구사항
현재 탐색하고 있는 경로에 대하여 active 처리(색상표시)한다.
Navigation Item 중 관리자 페이지는 우측 끝에 위치한다.
Navigation Item을 클릭 시 해당하는 페이지로 이동한다.
- [Tab 컴포넌트]
point
Category탭과 ProductList는 의존성을 가지지 않도록 한다.고민
[2. 필터링시 데이터 fetching 방법에 대한 고민]- 카테고리별 페이지 만들기
- dynamic 라우팅 by
getStaticPaths
- https://gamguma.dev/post/2022/01/nextjs-blog-development-review
요구사항
category 탭의 요소를 클릭하면 각 category에 해당하는 페이지로 이동한다.
현재 선택된 category는 active 처리(색상)한다.
- [List 컴포넌트]
- 반응형 구현
- emotion을 통한 media 처리하기

4. 상품 목록 CRUD 기능 구현
요구사항
[관리자 페이지]
접근을 위한 인가처리
SideBar를 통해 상품관리 페이지로 이동할 수 있다.
[데이터 테이블]
시맨틱 태그를 통해 테이블을 구현한다.
테이블 로직과 스타일을 구별하여 headless 한 형태로 구현한다.
상품목록을 테이블 형식으로 전부 확인할 수 있다. (
Read
)action Column이 존재할 경우, prop으로 JSX를 받아 렌더링 한다.
[
Advance
] 테이블은 상품을 10개씩 보여주며, 페이지네이션이 가능하다.[
Advance
] Column을 클릭 시, 해당 열을 기준으로 sort 된다.[상품 관리 Action 추가]
테이블 상단에는 액션바가 존재한다.
출력하기 버튼을 통해 현재 상품의 가격 List를 출력할 수 있다.
상품 추가 버튼을 통해 상품을 추가할 수 있다.(
Create
)Action Column의 버튼을 통해 상품을 수정/삭제할 수 있다. (
Update/Delete
)[
Advance
] 한 번에 여러개의 상품을 입고 처리한다.[상품 필터링]
액션바 위에는 categoryTab이 위치한다
[
Advacnce
] 제품 입고하기주문기능 구현 후
판매 처리하기납품 영수증 출력 기능
개발 계획
미리 커밋 메세지를 작성해보며 개발 계획/일정 세워보기
[feat] 데이터테이블 마크업 작성
[feat] 재사용을 위한 useTable Hook 추가
테이블 속성 설정
[상태]
판매중
,품절
,숨김
[재고]
- 재고 여부 지금에서 추가할지?
[feat] 타입스크립트 적용
[feat] 상품 추가하기
[feat] 상품 수정하기
[feat] 상품 삭제하기
[feat] 상품 필터링(
카테고리별
, 상태별
)[관리자 페이지 인가 처리]
[admin상품 관리]
이슈
데이터 interface 포맷팅 어디서 담당할 것인지- 현재 내려오는 stapi data를 그대로 앱 전반에서 사용하기에는 interface가 굉장히 불편하다. client에서 사용가능성이 높은 속성과 사용하기 편리하도록 formatting 해줄 필요가 있다.
어디서
- products에 대한 request가 발생하면, 전역에 state에 products를 저장한다.
- 저장할 때, formatting을 거친 형태로 저장한다
- 사용할 때, 필요한 속성을 골라서 사용한다.
어떻게
- attributes 속성 푼 상태로, 필요한 속성만 골라 담아서
- before
- After
example
{ id: 1, attributes: { title: "[담양농협] 풍광수토 20kg", imageUrl: "https://cdn-mart.baemin.com/sellergoods/main/0adaa81d-289a-4738-82bf-109bfd5ff091.jpg?h=400&w=400", price: 49900, description: "1kg당 250원", createdAt: "2022-11-11T02:56:27.322Z", updatedAt: "2022-11-11T03:02:38.491Z", publishedAt: "2022-11-11T02:56:30.924Z", slug: "20kg", category: { data: { id: 2, attributes: { name: "곡류", slug: "-2", createdAt: "2022-11-11T02:23:18.536Z", updatedAt: "2022-11-11T02:43:31.492Z", publishedAt: "2022-11-11T02:23:46.043Z", code: 100, }, }, }, sub_category: { data: { id: 1, attributes: { name: "일반미", slug: "10000", createdAt: "2022-11-11T02:46:02.742Z", updatedAt: "2022-11-11T02:51:34.734Z", publishedAt: "2022-11-11T02:51:34.732Z", code: 10000, }, }, }, image: { data: null, }, }, },
{ id: 1, title: "[담양농협] 풍광수토 20kg", imageUrl: "https://cdn-mart.baemin.com/sellergoods/main/0adaa81d-289a-4738-82bf-109bfd5ff091.jpg?h=400&w=400", price: 49900, description: "1kg당 250원", category: { name: "곡류", code: 100, }, sub_category: { name: "일반미", slug: "10000", createdAt: "2022-11-11T02:46:02.742Z", updatedAt: "2022-11-11T02:51:34.734Z", publishedAt: "2022-11-11T02:51:34.732Z", code: 10000, }, image: { data: null, }, },
- [feat] admin 상품추가

[데이터 테이블]
- [데이터 테이블 생성을 위한 라이브러리 개발 기록]
[Typescript 적용]
[Select 컴포넌트]
- [Select 컴포넌트 개발기록]
5. 상품 목록 인쇄 기능 구현
6. 납품 영수증 출력 기능
Ref
- Yarn workspace
진행상황 칸반
고민사항 모음
[1. 삼항연산자의 클린한 사용법 고민]
- 현재 상황
- path가 루트인 경우에만 다르게 검증하는 함수가 있는 상황
path === “/”
이면,path
===
currentPath
로 검증- 그외의 경우,
currentPath.startsWith
(path)
로 검증 - 삼항연산자를 사용할 경우, 직관적인 이해도에 대한 고민
- if-else가 더 직관적인 이해가 가능하지만, 파일 전체로 봤을 때 라인이 길어지며 가독성이 떨어지는 단점
삼항연산자
의 가독성 VSif-else문
의 직관성
- 해결 방법
- 나만의 기준가지기
- 1) Nullable 한 상황을 처리하는 경우
const name = isLogin ? getName() : "이름없음"
- 2) 삼항연산자를 사용해서 무언가의 값을 만들고 변수로 담아낼 때
const name = isLogin ? getName() : "이름없음"
- 3) 함수가 내뱉는 값이 간단할 때
return isAdult ? '입장이 가능합니다' : '입장이 불가합니다'
아래 3가지 경우가 아닌 이상, if-else문 또는 switch문 사용하기
- 액션 및 결론
삼항 연산자 사용의 3가지 기준에 해당하지 않으므로, if-else문으로 직관적 이해를 높이는 코드 작성 (+early-return방식)
const isActive = (path) => { if (path === "/") { return path === currentPath; } return currentPath.startsWith(path); };
삼항연산자를 올바르게 사용하기 (by poco)
## 2. 삼항 연산자 다루기 > 삼항연산자의 일관성 : 삼항 연산자를 이용한 나만의 방법이 있는가? 숏코딩에 주로 사용하는가? : 삼항연산자를 사용함에 있어서 일관성을 가지는 것이 중요하다. > 삼항연산자 조건 : 삼항연산자는 3개의 `피연산자`를 가진다. : `조건` ? `참`[식] : `거짓` [식] : 참과 거짓 연산자는 `식`임에 주의 (if문, for문 불가) > 01.삼항연산자 BadCase : if-else 문을 사용하는 것이 훨씬 더 직관적으로 이해하기 좋다 : if-else 문 보다는 switch문을 이용하는 게 더 적합하다. : edge케이스를 else로 처리하는 것보다 default로 처리하는 것이 현업에서 더 자주 사용 ```js function example() { return condition1 ? value1 : condition2 ? value2 : condition3 ? value3 : value4; } function example() { if (condition1) { return value1; } else if (condition2) { return value2; } else if (condition3) { return value3; } else { return value4; } } function example() { const condition = condition1 || condition2 || condition3 || condition4; let value = ""; switch (condition) { case conditon1: value = value1; break; case conditon2: value = value2; break; case conditon3: value = value3; break; default: value = value4; } return value; } ``` > 02.삼항연산자 BAD Case : 삼항연산자가 중첩되어 있기 때문에, 직관적으로 파악하기 어렵다. : 따라서 사람중심적으로 생각하여 이해가 쉽도록 한다. : `괄호`를 통래 우선순위를 직관적으로 파악할 수 있도록 한다. ```js //BAD CASE const example = condition1 ? (a === 0 ? "zero" : "positive") : "negative"; //BETTER CASE const exampler = condition1 ? (a === 0 ? "zero" : "positive") : "negative"; ``` > 03.삼함연산자 GODD CASE > : Nullable 한 상황에서 예외처리를 위해 삼항연산자를 사용할 수 있다. ```js const welcomeMessage = (isLogin) => { const name = isLogin ? getName() : "이름없음"; return `안녕하세요 ${name}`; }; //if문을 이용한 BAD CASE const welcomeMessage = (isLogin) => { if (isLogin) { return `안녕하세요 ${getName()}`; } return `안녕하세요 이름없음`; }; ``` > 04.삼항연산자 BAD CASE : alert는 인자에 상관없이 'undefined'를 반환하기 때문에, 사실상 isAdult에 따라서 같은 값을 반환하고 있다. : 따라서 삼항연산자의 본질보다는 `숏코딩`에 가까운 CASE이다. ```js function alertMessage(isAdult) { isAdult ? alert("입장이 가능합니다.") : alert("입장이 불가능합니다."); } //BETTER CASE function alertMessage(isAdult) { if (isAdult) { alert("입장이 가능합니다."); } else { alert("입장이 불가능합니다."); } } ``` > 05.Poco의 삼항연산자 사용 CASE : 1. 삼항연산자를 사용해서 무언가의 값을 만들고 변수로 담아낼 때 `const name = isLogin ? getName() : "이름없음"; ` : 2. 함수가 내뱉는 값이 간단할 때 `return isAdult ? '입장이 가능합니다' : '입장이 불가합니다'; ` ### 요약 삼항연산자는 3개의 피연산자를 가지고, 피연산자는 `식`임에 주의해야한다. 삼항연산자는 주로 조건에 따라 다른 값을 뱉어낼 때 사용된다. 삼항연산자를 중첩해서 사용한다면 사용자가 직관적이해가 어려울 수 있으므로 if-else문, switch문을 고려하고, 단순 숏코딩을 위해서 사용하는 것이 아닌지 체크한다. 삼항연산자 사용은 자신만의 기준을 가지고 일관성 있게 사용하는 것이 바람직하다.
[2. 필터링시 데이터 fetching 방법에 대한 고민]
- 현재 상황
- 카테고리를 선택하면 해당하는 카테고리의 상품목록을 보여주어야 한다.
- 이 때 필터링된 상품목록을 보여주는 2가지 방식이 존재한다.
- 1번) 카테고리에 해당하는 모든 상품목록을 정적페이지로 만들어두기 (
SSG
) - 2번) 첫 페이지에서 모든 상품목록을 가져온 후(
SSG
), 탭 글릭 시 상품목록에 필터함수를 적용하여 상품목록을 리렌더링 시킨다. (CSR
)
- 해결 방법
- 1번 방식, 즉 카테고리 별로 모든 페이지를 미리 정적 페이지로 생성해두고, 탭 클릭 시 페이지 이동을 발생시키는 방법을 선택
- 선택의 근거는 2가지
- 카테고리 페이지 별 데이터는 변하지 않기 때문에 충분히 build 시간에 만들어 둘 수 있다. (SSG를 사용하기에 적합하다)
- 데이터 개수의 많고 적음에 상관 없이, 일정한 로딩속도를 제공할 수 있다.
- 카테고리 별 meta태그를 적용할 수 있다.
- 액션 및 결론
- 이번 고민은 사실 data fetching 방식에 대한 고민이었다고 생각한다.
- Next를 처음 사용하기에 선택할 수 있는 3가지 방식(CSR, SSR, SSG)에 대해서 고민이 있었다. 이번 기회에 충분하게 해당 방식들의 장단점을 이해할 수 있게 되었다,
[3. data formating의 위치와 방법]
- 현재 상황
- strapi API로 부터 받아온 raw data를 그대로 front에서 사용하는 것이 불편하다
- 통일된 interface를 가지고 있지 않아, 필요시 마다 가공이 필요하기 때문에
ex)
Table컴포넌트
- 해결 방법
가공위치
- 1번방안: API를 받아오는 순간, formatting을 바로진행
- 2번방안: 사용시, format 함수를 불러와서 사용
선택
- 1번 방안
- front에서 필요한 data Interface를 미리 정의하고, 이에 맞추어
API를 받아온 직후 formatting
을 해주어야지 , ‘SingleSourceOfTruth’ 를 지킬 수 있다. - api 요청을 한 곳에서 관리하고, formatting이 필요한 경우 이곳에서 가공 후 response를 반환시켜 주도록 한다. (ex.
/api/product.ts
,/api/user.ts
)
[why]
[how]
- 액션 및 결론
1단계
사용중인 api를 함수화하여,api/
에서 관리2단계
decode/incode 함수 정의하기3단계
컴포넌트 단에서는 decode/incode된 api response 사용
목표: API Wrapping하기
[4. dataTable 잘 만든 사례로 작성해보기]
- 커스텀 가능성 고려하기

1번
useTable을 이용하여, headless한 dataTable 컴포넌트 (react-table 라이브러리 방식)
2번
dataTable을 보여주기 위한 역할, 보여줄 데이터를 관리하는 것과 분리 (Search, Filter), 둘 간의 연결은 query parameter를 통해 느슨하게 연결
2번 방식
- DataTable이 너무 많은 역할과 책임을 가지지 않도록 하는 것에 focus
- 즉, 데이터를 보여주는 역할 이외는 모두 외부로 위임하도록 설계
- filter와 search를 위한 parameter는 urlQuery를 통해 받아 적용한다.
- 테이블의 데이터에 대한 접근/액션은 column의 render함수를 열어두어 해결한다.
- 장점
- filter와 Table이 직접적으로 연결되어 있지 않기 때문에, 두 컴포넌트 모두 재사용이 가능하다.
- filter를 통해, Table이 아닌 List를 보여주는 등
- 단점
자잘한 에러로그 / Tip
[error]
- emotion css prop 적용하기
문제
: css prop을 next에서 사용하기 위해서는 css 코드를 트랜스파일 해주는 babel 설정이 필요했다.해결
: next12 부터는 swc 컴파일러가 next.js의 컴파일러로 동작하면서, 이러한 부분을 대신 처리해줄 수 있다.How
// next.config.js const nextConfig = { compiler: { emotion: true, }, ... };
- router In useEffect
문제상황
: 페이지 이동 시 url에서 쿼리를 가져와 useTable의 인자로 넣어주는 코드
next의 router 객체가 리렌더링 되며, useTable 훅과 DataTable이 무한으로 리렌더링 되는 에러 발생
원인
: router.query 객체는 초기 값 undefind 인 상태에서, 모든 페이지 hydration 이후 router객체에 query 값이 할당 된다.
즉 useEffect 실행 이후, router.query에 현재 페이지 url params 가 담기게 된다.해결
: router.isReady 함수를 통해, 준비 된 이후, useEffect 내의 로직이 실행되도록 바꾸어준다.
ref
[Tip]
- 특정 요소만 오른쪽 정렬 하는 방법

.flex-container { display: flex; } .flex-item { flex: none; /* flex: 0 0 auto */ } .flex-item-right { margin-left: auto; }