이제 npm에 배포를 해보자. 1. npm 계정 생성 npm에 배포하려면 당연히 계정이 있어야 한다. npm 공식페이지에서 가입! 2. npm 로그인 npm 웹페이지에서 로그인 하는것이 아니라 터미널에서 로그인하자. bash npm login 아이디, 비밀번호 등 요구하는 정보들을 입력하면 로그인 완료! 3. npm 배포 드디어 배포할 차례이다. 이전에 '배포'라는 단어만 들어도 막 엄청 웅장하고 복잡한줄만 알았는데 막상 해보고 나니 별 것 아니었다. bash npm publish --access public // 최초 배포시 npm publish // 최초 배포 이후 ❗ npm 패키지명에 관하여 직관적이고 간단한 패키지명은 이미 존재할 확률이 높다. npm 공식페이지에 검색 후 프로젝트를 생성하자. 하지만 중복을 피할 방법이 있다. bash @siyeol/awesome-package 이처럼 이름앞에 @name/을 붙이는 것이다! npm - scope 참고. 패키지명 앞에 스코프를 추가하려면, npm 공식페이지 - 계정 설정에서 Organizations를 생성하면 된다. Organizations를 추가하지 않고 프로젝트 이름에 스코프를 추가해서 배포하면 scope를 찾을 수 없다는 오류가 나올것이다. 이제 npx create-next-boilerplate 명령어 한줄로 nextjs + react-query + zustand + eslint/prettier 가 세팅된 프로젝트를 바로 시작할 수 있다! 하지만 단점은 각 패키지의 버전을 따로 관리해줘야 한다는 점이다. 최종 목표는 사용자가 동적으로 boiler plate를 생성하는, 진정한 create-next-app을 만드는 것이다. 끝! > 배포 정보는 create-next-boilerplate 에서 확인할 수 있다.
1. boiler plate 만들기 2. git clone, npm i를 자동으로 수행하는 프로젝트 만들기 3. npm에 배포하기 위의 순서대로 진행해보자. ✨ Boiler plate 만들기 1. npx create-next-app으로 next 프로젝트를 시작하자. 나는 Tailwind를 쓰지 않고 TypeScript, ESlint, App router를 사용했다. 2. ESLint/Prettier 설정하기 항상 프로젝트를 시작할 때 ESLint와 Prettier를 설정하는게 매우 귀찮았다. 미리 설정해놓자. 내가 사용한 패키지는 아래와 같다. json "devDependencies": { "@typescript-eslint/eslint-plugin": "^7.11.0", "@typescript-eslint/parser": "^7.11.0", "eslint": "^8", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^18.0.0", "eslint-config-next": "14.2.3", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-prettier": "^5.1.3", "prettier": "^3.2.5", } ESLint, Prettier 설정은 다음과 같다. json // .eslintrc.json { "parserOptions": { "project": "./tsconfig.json" }, "plugins": "@typescript-eslint/eslint-plugin", "extends": [ "airbnb-base", "airbnb-typescript/base", "plugin:@typescript-eslint/recommended", "plugin:prettier/recommended" ], "rules": { "import/extensions": [ "error", "ignorePackages", { "": "never" } ], "react/react-in-jsx-scope": "off", "react/jsx-props-no-spreading": "off" } } json // .prettierrc { "printWidth": 80, "tabWidth": 2, "useTabs": false, "semi": true, "singleQuote": true, "jsxSingleQuote": true, "trailingComma": "all", "bracketSpacing": true, "proseWrap": "never", "endOfLine": "auto" } 3. react-query 설정하기 Next.js + react-query 설정하기 포스트 내용과 같이 설정했다. 4. zustand 설정하기 Next.js + zustand 설정하기 포스트 내용과 같이 설정했다. 위 내용과 같이 프로젝트 초기 설정을 마치고 github에 업로드 했다. template repository로 설정을 했기 때문에 새 repo를 만들 때 템플릿으로 지정을 하면 기본적으로 위 설정과 똑같은 새 프로젝트가 생성이 된다. 하지만 나는 간지나게? 터미널 명령어 한 줄로 위 설정을 가져오고싶었다. ✨ git clone, npm i를 자동으로 수행하는 프로젝트 만들기 create-next-boilerplate 라는 새 프로젝트 폴더를 만들었다. 1. npm init -y 물어보는 질문 모두 yes! 2. package.json 구성하기 json // package.json { "name": "create-next-boilerplate", "version": "1.1.0", "description": "Next.js 14 app router boilerplate with Zustand and React Query", "bin": { "create-next-boilerplate": "./bin/create-next-boilerplate.js" }, "keywords": [ "nextjs", "zustand", "react-query", "boilerplate", "setup" ], "author": "siyeol97", "license": "MIT", "homepage": "https://github.com/siyeol97/nextjs-boilerplatereadme", "bugs": { "url": "https://github.com/siyeol97/create-next-boilerplate/issues", "repository": { "type": "git", "url": "git+https://github.com/siyeol97/create-next-boilerplate.git" } }, "type": "module", } bin 속성을 이용해서 ./bin/create-next-boilerplate.js 파일이 create-next-boilerplate 명령어로 등록되어 사용자는 npx create-next-boilerplate 을 터미널에서 전역 명령어로 실행할 수 있다! 나머지 속성들은 npm 에 등록될 때 정보들을 제공한다. 3. create-next-boilerplate.js 구현하기 사용한 라이브러리는 다음과 같다. - inquirer : 터미널에서 사용자 입력을 인터랙티브하게 받을 수 있도록 해준다. '프로젝트 이름'을 입력받을 때 사용했다. - simple-git : node 환경에서 git 명령어를 쉽게 실행할 수 있게 해준다. git clone명령어를 실행할 때 사용했다. - execa : node 외부 명령어를 실행하고 결과를 쉽게 다룰 수 있게 해준다. npm install 명령어를 실행할 때 사용했다. 전체적인 코드는 다음과 같다. js // create-next-boilerplate.js !/usr/bin/env node import inquirer from 'inquirer'; import simpleGit from 'simple-git'; import { execa } from 'execa'; import path from 'path'; import fs, { rmSync } from 'fs'; const REPO_URL = 'https://github.com/siyeol97/nextjs-boilerplate.git'; async function promptProjectName { const answers = await inquirer.prompt => , }, ]); return answers.projectName; } async function cloneRepo { const targetDir = path.join, projectName); const git = simpleGit; if ) { console.error; process.exit; } console.log; await git.clone; console.log; const gitDir = path.join; rmSync; console.log; const packageJsonPath = path.join; const packageJson = JSON.parse); packageJson.name = projectName; fs.writeFileSync); console.log; await execa; console.log; } async function main { try { const projectName = await promptProjectName; await cloneRepo; } catch { console.error; } } main; 로직은 간단하다. 1. npx create-next-boilerplate 를 실행하면 2. 터미널에서 프로젝트 이름을 입력받고 3. 해당 이름으로 파일을 생성한 후 4. 앞서 만들어 두었던 boiler plate repository를 clone하고 5. .git 파일을 삭제한 후 package.json의 name을 입력받은 프로젝트 이름으로 수정한다. 6. 마지막으로 npm install 명령어를 실행! 마지막 단계인 npm 배포는 다음 포스트에!
여러 프로젝트를 경험하고 난 후에 드는 생각이 > 프로젝트를 시작할때 마다 npx create-next-app 하고 다른 라이브러리 설치하고,, 하는 과정이 너무 귀찮은데? 개인적으로 자주쓰는 세팅을 미리 만들어서 명령어 한줄로 설치하고싶다! 라는 생각이 들었다. 내가 자주 사용할것 같은 구성은 아래와 같다. 1. next.js 14 app router 2. typescript 3. react-query 4. zustand 5. eslint/prettier Boiler Plate ❓ 보일러 플레이트란 묻지도 따지지도 않고 따라 적는 코드, 수정 없이 반복적으로 사용되는 코드를 의미한다. 프로젝트를 시작하기 위해 npx create-react-app 이나 npx create-next-app, npm i zustand npm i prettier 등등을 반복했다. 이런 일련의 과정을 미리 수행해서 하나의 템플릿 으로 만들어 놓으면, 초기 세팅 시간을 크게 단축 시킬것이다. 어떻게 만드는데? 여러 블로그을 검색해본 끝에 몇 가지 방법이 있다고 한다. 1. 첫 번째 방법 - boiler plate를 github 저장소에 올린다. - 필요할 때 마다 clone한다. - git 연결을 끊기 위해 .git 폴더를 삭제한다. 이 방법은 엄청 간단하지만 .git 폴더를 삭제하는 과정이 추가되어 귀찮을 수 있다. 2. 두 번째 방법 - boiler plate를 github template repository에 올린다. - github에서 새 repository를 만들때 template을 선택한다. bash gh repo create --template="" OR gh repo create --template="" 위의 명령어로 간단하게 만들 수 있다고 한다. 참고 3. 세 번째 방법 - boiler plate를 github 저장소에 올린다. - 그 boiler plate를 clone하고, .git을 삭제하고 npm i를 자동으로 수행해주는 프로젝트를 만들고 npm에 배포한다. - ex) npx create-next-boilerplate 명령어로 설치한다. 4. 네 번째 방법 - create-next-app처럼 CLI 기반에서 동적으로 선택할 수 있는 boiler plate를 만들고 npm에 배포한다. 네 번째 방법처럼 사용자가 원하는 기술을 선택해서 boiler plate를 생성하면 범용성이 더 좋아지고 유지 보수하기에도 쉽다. 하지만 패키지의 버전 의존성등 생각해야할 것이 많아지고 구현하기 어렵고 오래걸릴 것이다. 일단 세 번째 방법으로 구현하고, 추후에 네 번째 방법으로 발전시켜 나가기로 했다. 자세한 구현 방법은 다음 포스팅에..
무한스크롤과 optimistic update를 구현하기 위해 next.js에 react-query를 사용하기로 했다. next.js 14 app router에 react-query를 설정하는 방법을 기록하자 1. Provider 생성 1. 프로젝트 root 경로에 providers 폴더 생성 2. ReactQueryProvider.tsx 생성 tsx 'use client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { useState } from 'react'; function ReactQueryProvider { const client = useState); return ; } export default ReactQueryProvider; new QueryClient 인스턴스를 state로 선언하는 이유 - useState를 사용하여 QueryClient 인스턴스를 생성하면, 컴포넌트가 다시 렌더링되더라도 동일한 QueryClient 인스턴스가 유지된다. - 만약 useState를 사용하지 않고 단순히 const client = new QueryClient를 사용하면, 컴포넌트가 리렌더링될 때마다 새로운 QueryClient 인스턴스가 생성된다. 이는 불필요한 인스턴스 생성과 데이터 캐싱의 중복을 초래한다. - ReactQueryDevtools 는 개발 모드일 때 여러 쿼리를 시각적으로 볼 수 있는 관리 툴이다. 2. 최상위 layout.tsx 에서 children 감싸기 tsx import ReactQueryProvider from '../providers/ReactQueryProvider'; import './globals.css'; export default function RootLayout { return ; } 최상위 레이아웃에서 ReactQueryProvider 로 감싸주는 이유 - 모든 컴포넌트가 React-Query의 컨텍스트에 접근할 수 있다. 이를 통해 모든 페이지와 컴포넌트가 동일한 QueryClient 인스턴스를 공유하고, 새로운 페이지나 컴포넌트를 추가할 때마다 개별적으로 QueryClientProvider를 설정할 필요가 없다. 3. 컴포넌트에서 useQuery 사용하기 tsx const { data, isLoading } = useQuery => getReplyList, }); 끝!
프로젝트에서 전역상태관리 라이브러리로 Zustand를 사용하기로 했다. Zustand를 선택한 이유는 - redux는 약간 복잡해서 러닝 커브가 높다. - 가벼워서 번들 크기를 줄일 수 있다. - 최근 1년간 Zustand의 다운로드 수가 많다. ✨ React 프로젝트에서의 사용법 그저 store를 만들고, 컴포넌트에서 사용하면 된다. tsx import { create } from 'zustand'; interface SidebarState { isSidebarVisible: boolean; toggleSidebar: => void; } const useSidebarStore = create => => set => ), })); export default useSidebarStore; tsx import useSidebarStore from '../stores/sidebarStore'; const Sidebar = => { const { isSidebarVisible, toggleSidebar } = useSidebarStore; return ; }; export default Sidebar; ✨ Next.js app router에서의 사용법 provider를 만들고 layout.tsx에 추가해야한다. 그 이유는 1. 서버가 동시에 여러 요청을 처리할 수 있기 때문에, 각 요청에 대해 독립적인 스토어 인스턴스를 생성해야 한다. 이를 위해 각 store마다 provider를 사용하면 안전하게 요청별 store을 관리할 수 있다. 2. provider를 사용하면 서버에서 렌더링될 때와 클라이언트에서 다시 렌더링될 때 동일한 초기 상태를 유지할 수 있습니다. 이를 통해 hydration 오류를 방지할 수 있다. 3. Next.js의 클라이언트 사이드 라우팅을 지원하기 위해, 각 컴포넌트에서 store를 초기화할 수 있는 provider를 사용하면 상태 관리를 용이하게 할 수 있다. 간단하게 말해서 Next.js의 SSR 환경에서 발생하는 동시 다발적인 요청 처리와 client-server간의 상태 일관성 유지를 위해 각각의 store를 독립적으로 관리해야 한다. SSR을 사용하지 않고, CSR을 사용한다면, React 에서의 사용법과 같다. 예를 들어, SSR을 사용하고, 모달의 상태를 전역으로 관리하고 싶다면 1. store 만들기 tsx import { createStore } from 'zustand/vanilla'; import { ModalType } from '@/types/types'; export type ModalState = { isOpened: boolean; modalType: ModalType; }; export type ModalActions = { toggleModal: => void; setModalType: => void; }; export type ModalStore = ModalState & ModalActions; export const initModalStore = : ModalState => { return { isOpened: false, modalType: null }; }; export const defaultInitState: ModalState = { isOpened: false, modalType: null, }; export const createModalStore = => { return createStore => => set => ), setModalType: => set => ), })); }; 2. provider 만들기 tsx 'use client'; import { type ReactNode, createContext, useRef, useContext } from 'react'; import { type StoreApi, useStore } from 'zustand'; import { ModalStore, createModalStore, initModalStore, } from '@/stores/modalStore'; export const ModalStoreContext = createContext | null>; export interface ModalStoreProviderProps { children: ReactNode; } export const ModalStoreProvider = => { const storeRef = useRef>; if { storeRef.current = createModalStore); } return ; }; export const useModalStore = => T): T => { const modalStoreContext = useContext; if { throw new Error; } return useStore; }; 3. layout.tsx에 provider 추가하기 tsx import type { Metadata } from 'next'; import '../styles/globals.scss'; import { ModalStoreProvider } from '../../providers/ModalStoreProvider'; export const metadata: Metadata = { title: 'Create Next App', description: 'Generated by create next app', }; export default function RootLayout { return ; } 3-1 route별로 store를 따로 생성 tsx // src/app/page.tsx import { CounterStoreProvider } from '@/providers/ModalStoreProvider' import { HomePage } from '@/components/pages/home-page' export default function Home { return } 4. useModalStore - 컴포넌트에서 사용하기 tsx export default function Floating { const { isOpened, toggleModal, modalType, setModalType } = useModalStore => state, ); return ... > Zustand 공식문서 참고
유저의 팔로우 목록 모달을 만들던 중, UI가 렌더가 될 때 깜빡이면서 내용이 달라졌다. 이 부분이 매우 거슬렸기 때문에 해결법을 찾았다. 결론부터 말하자면, useEffect와 useLayoutEffect는 실행 시점만 다르다. ✨ useEffect useEffect 훅은 컴포넌트가 렌더링된 후, DOM을 업데이트 하고 paint가 완료된 후 비동기적으로 실행된다. tsx useEffect => { // 데이터 페칭 const fetchData = async => { const response = await fetch; const data = await response.json; setData; }; fetchData; },) 데이터가 state에 반영되고 state 변경으로 인해 컴포넌트가 다시 렌더링 되기 때문에 화면이 깜빡이면서 바뀐다. ✨ useLayoutEffect useLayoutEffect 훅은 컴포넌트가 렌더링되고 DOM이 업데이트된 직후 paint 되기 전에 동기적으로 실행된다. tsx useLayoutEffect => { if { const element = document.getElementById; if { element.style.height = ${data.length 10}px; // DOM 조작 } } }, data) 데이터가 state에 반영되고 state 변경으로 인해 컴포넌트가 다시 렌더링 되지만, paint가 되기 이전에 동기적으로 실행되었기 때문에 사용자는 화면의 깜빡임을 보지 않는다. ✔ 프로젝트에 어떻게 적용했냐면 tsx useLayoutEffect => { if { setFollowData; } if { setFollowData; } }, modalType); 처음에는 useEffect훅을 사용했더니, 모달의 타입이 바뀔때 마다 이전 상태값이 잠깐 보였다가 바뀌었다. useLayoutEffect 훅을 사용하여 브라우저가 화면을 다시 그리기 전에 상태값이 반영되어 깜빡임 없이 내용을 볼수 있게 된다. ❗ 주의할 점 1. useLayoutEffect는 동기적으로 실행되므로 복잡한 DOM조작이 많은 경우 컴포넌트가 렌더링될 때 까지 다음 렌더링이 지연될 수 있다.그래서 DOM 조작이 길어질 경우 화면에 paint가 되지 않아 사용자 경험을 저하시킬 수 있다. 2. 데이터 페칭과 같은 일반적인 사이드 이펙트를 실행할 때는 useEffect를 사용하는 것이 좋다.
2024.03.07 ~ 2024.03.25 Codeit FE Sprint 중급 프로젝트)를 진행했다. 프로젝트를 진행하면서 느꼈던 점과 다짐들을 KPT 형식으로 적어본다. 👍 Keep 1. 프로젝트 로드맵에 맞춰 마감 기한을 엄수했다. - github issue, 로드맵 기능을 이용해 일정을 관리했다. - 공통 컴포넌트, 유틸함수 등 다른 팀원이 필요한 작업물을 우선적으로 완료했다. - 다른 팀원이 도움을 요청한 문제를 함께 해결하여 마감 기한을 맞출 수 있었다. - presigned URL을 받고 이미지를 S3에 업로드하는 과정에서 어려움을 겪었다. - 이미지 파일을 e.target.files?.0 로 가져오면 된다는 것을 알려주었다. - const imageUrl = presignedUrl.split0; '?' 뒤의 쿼리스트링 앞부분을 가져오면 된다는 것을 알려주었다. 2. 맡은 기능을 기한 내에 빠짐없이 구현했다. - 공고 목록 페이지 제작 - 마감, 지난 공고 표시 기능 - 공고 마감임박순, 시급많은순, 시간적은순, 가나다순 정렬 기능 - 주소, 시작일, 시급 필터링 기능 - 유저 주소 맞춤 추천 공고 기능 - 공고 검색 기능, 최근 본 공고 표시 기능 - 공고 지원/취소 기능 3. 코드 컨벤션과 협업 규칙을 준수했다. - 내가 작성한 코드를 팀원들이 이해하기 쉽게 JsDoc을 작성했다. - 팀원의 PR이 올라오면, 최소한 6시간 안에 코드 리뷰를 작성하는 나만의 룰을 지켰다. 원래의 규칙은 24시간 안에 리뷰를 작성하는 것이다. 🤔 Problem 1. 데이터를 fetch 하는 로직과 최적화에 익숙하지 않은 것 같다. - 공고 목록 페이지의 공고 카드 컴포넌트 각각에 해당 공고가 날짜가 지난 공고인지, 마감된 공고인지 확인하는 로직을 만들었다. 그 결과 공고 목록 페이지에 접속하면 공고 카드의 갯수만큼 API 요청이 날라간다. 서버 컴포넌트 기능이나 react-query를 사용하면 이러한 문제를 해결할 수 있을 것 같다. 2. 확장성 있는 컴포넌트를 만들지 못한 것 같다. - 공고 상세 페이지와 사장님 공고 상세 페이지에 쓰이는 공고 상세 컴포넌트를 만들었다. 하지만 사장님 공고 상세 페이지에 들어가야 하는 데이터 형식이 따로 있었는데, 그것을 고려하지 못한 채 컴포넌트를 설계했다. 컴포넌트에 쓰이는 유틸함수의 관심사 분리와 prop을 더욱 범용성 있게 받을 수 있도록 설계해야 겠다. 3. 데이터 로딩 상태 처리를 적절하게 하지 못한 것 같다. - 공고 카드와 공고 상세 데이터를 불러올 때, 그 공고가 지난/마감 공고라면 UI가 깜빡 하면서 재렌더링이 되는 문제가 있었다. 팀원들과의 회의에서 UI가 깜빡 거리는 현상을 보이지 않게 하기 위해 로딩 상태일 때 로딩 스피너를 보여주는 컴포넌트로 해결하기로 했다. - 하지만 이러한 방식도 문제가 있는 것 같다. UX적인 측면에서 아무 UI도 보이지 않고 로딩 스피너만 보여지는 상황보다, 조금 깜빡 거리고 재렌더링이 되더라도 UI가 보여지는 면이 더 나은 것 같다. - 이러한 문제를 해결하기 위해, 스켈레톤 UI를 고려하거나, 데이터가 전부 불러와진 후 렌더링 되도록 서버 컴포넌트를 이용하는 방법을 고려해야겠다. 🔥 Try 1. 서버 컴포넌트 기능과 react-query를 적극적으로 사용해야겠다. - 대부분 'use client' 클라이언트 컴포넌트로 구현했는데, 앞으로는 특히 데이터 fetch 기능에 있어서 서버 컴포넌트와 react-query를 사용해야겠다. - react-query를 사용해서 페이지네이션, 무한 스크롤 기능과 prefetch 기능을 적극적으로 사용할 것이다. 2. 범용성 있는 컴포넌트를 만드는 연습을 해야겠다. - 특정 도메인에 종속되지 않는 컴포넌트와 함수를 만드는 것이 중요한 것 같다.간단한 button, input에서 시작해서 여러 군데에서 쓰이는 공통된 컴포넌트를 만들 때 어떤 형식으로 prop을 받을 것인지, 어떤 형태로 return 할 것인지 잘 생각해서 만들어야겠다. 3. 앞으로도 마감 기한을 엄수해서 맡은 역할을 수행할 것이다. - 제일 중요한 사항이 마감기한을 엄수해서 맡은 역할을 수행하는 것이라고 생각한다.아무리 좋은 코드를 작성하고 기발한 기능을 구현한다고 해도 마감기한을 지키지 못하면 프로젝트의 성공으로 이어지기 어렵기 때문이다. - 지금까지 해왔던 것처럼, 어려운 부분이 있으면 팀원들과 함께 커뮤니케이션을 적극적으로 해 문제를 해결하고 마감 기한에 맞춰 프로젝트를 성공적으로 마무리 해야겠다!
✨ Server Component vs Client Component 👉🏻 Rendering - jsx로 작성된 React component를 동적으로 화면 UI를 생성하고 업데이트 하는 과정 👉🏻 CSR - 모든 HTML, CSS 및 JavaScript 파일이 브라우저로 다운로드되어 클라이언트 측에서 UI를 동적으로 생성. - 초기 로딩 속도를 희생하고, 동적으로 콘텐츠를 업데이트하고 사용자 경험을 향상시키는 데 중점을 둠. 👉🏻 SSR - 서버 측에서 UI를 생성하는 방법. SSR에서는 서버에서 초기 HTML을 렌더링한 후에 이를 클라이언트로 전송하여 화면을 보여준다. - 초기 로딩 속도를 개선하고 SEO를 향상시키는 장점이 있으나, 서버 부하와 성능 이슈를 고려해야 한다. React 18, Next.js 13부터 Server Component가 도입, 지원된다. server component, client component 두 가지 형식으로 컴포넌트를 만들 수 있다. ✅ Server Component - next.js 에서는 /app 하위에 컴포넌트를 만들면, 디폴트로 server component이다. - server단에서 렌더링 되므로, client단의 코드는 사용이 불가능하다. - event listener - React Hooks - Browser API - server component는 client component를 포함할 수 있다. - 불필요한 dependency를 client에 전송할 필요가 없다. ✅ Client Component - 컴포넌트 최상단에 'use client' 를 추가해주면 된다. - client단 에서만 rendering 되는게 아닌, backend에서 정적 HTML로 render되고, frontend에서 hydrate 및 interactive 된다. > hydration: 정적 html을 로드하고나서, js, react를 적용해 interactive한 요소들로 채워지는 과정 Next.js 공식문서에서 권고하는 server component를 사용해야하는 경우 - Fetch data - Access Backend Resources - Keep sensitive information on the server - Keep large dependencies on the server / Reduce client-side JavaScript ✨ Client Component에 포함되는 자식 컴포넌트들은 모두 Client Component 일까? 정답부터 말하자면 ❌틀렸다❌ 🔴 client component 내부에서 server component를 import 할 수 없다. tsx 'use client' // You cannot import a Server Component into a Client Component. import ServerComponent from './Server-Component' export default function ClientComponent { const count, setCount = useState return => setCount}>{count} ) } 🟢 client component에 chidren prop으로 전달되는 server component는 여전히 server component이다. tsx 'use client' import { useState } from 'react' export default function ClientComponent { const count, setCount = useState return => setCount}>{count} {children} ) } tsx // This pattern works: // You can pass a Server Component as a child or prop of a // Client Component. import ClientComponent from './client-component' import ServerComponent from './server-component' // Pages in Next.js are Server Components by default export default function Page { return } 이 경우 server component와 client component는 decoupled 되고, 독립적으로 rendering 된다. 출처 : next.js 공식문서
✨ 컴포넌트 설계, data fetching tsx const NoticeList = => { const { filterCondition, sortCategory, currentPageNumber, listCategory, noticeItemList, updateItemList, updateFilterCondition, applyFilter, updateSortCategory, updatePageNumber, setListCategory, setNoticeItemList, } = useNoticeListState; return : null} {noticeItemList.items.length !== 0 ? ; }) ) : } <Pagination count={noticeItemList.count} currentPageNumber={currentPageNumber} updatePageNumber={updatePageNumber} setNoticeItemList={setNoticeItemList} sortCategory={sortCategory} listCategory={listCategory} searchValue={searchValue} filterCondition={filterCondition} /> ); }; export default NoticeList; 공고 목록 리스트를 보여주는 컴포넌트를 만들었다. props로 넘겨줄 상태를 관리하는 부분과 커스텀 훅으로 뺀 건 이제 익숙하게 할 수 있겠다는 생각이 들었다. 하지만 부족했다고 생각하는 점이 있다. 1. 공통 컴포넌트를 만들었는데, 특정 도메인이 묻은 점 - 특정 도메인이 묻지 않은 범용성 있는 컴포넌트를 만드는 것이 어려운 것 같다. 기능 개발에 급급해 코드를 작성하다 보면, 항상 도메인이 묻어 나오는 것 같다. 2. 데이터 상태 관리에 아직 익숙하지 않다는 점 - next.js 에서 react-query는 필요하지 않을 수도 있다는 글을 봤다. - next.js를 사용하지 않는 react 프로젝트에서 react-query 를 사용해 상태를 관리, 또는 next.js - server component 에서 확장된 fetch 함수 - 캐싱, revalidate 같은 기능을 이용해서 data fetching을 해야겠다. 3. 부모 컴포넌트에서 데이터를 넘겨줄지, 자식 컴포넌트에서 fetch 해올지 고려하지 않고, 무분별하게 데이터를 fetching 했다는점. - 이 부분을 확실하게 하지 않고 컴포넌트를 만들다 보니, 이중으로 데이터를 불러온다거나, UI가 깜빡거리면서 재렌더링이 되어 성능과 UX 둘 다 만족스럽지 않았다. ✔ next.js에서 권장하는 data fetching 방법 - 공식 문서 a. server component를 이용하여 서버측에서 데이터를 fetching 합니다. b. data fetching은 병렬적으로 수행하는것이 좋습니다. c. layout 및 page의 경우 데이터를 사용하는 곳에서 데이터를 가져오는것이 좋습니다. d. Suspense, Loading UI를 이용해 점진적으로 렌더링을 수행하는것이 좋습니다. 위 사항을 고려해서 컴포넌트를 설계 해야겠다.
03.15 ~ 03.16 ✨ 객체 상태 업데이트 tsx const filterCondition, setFilterCondition = useState; const updateFilterCondition = => { setFilterCondition); }; React는 상태가 변경되었는지를 판단하기 위해 객체 참조를 사용한다. 따라서 이전 객체와 새로운 객체가 동일한 참조를 가지고 있다면 React는 상태 변경을 감지하지 못한다. 상태 변화를 감지해 재렌더링을 유발하려면, 객체를 복사해 완전히 새로운 객체를 생성하고, 요소를 변경시켜야 한다. 그러면 객체의 참조값이 달라져 React는 상태변화를 감지해 재렌더링을 수행한다.
03.13 ~ 03.14 ✨ 정렬 기능 처음 구현한 로직은 먼저 전체 리스트를 불러온 다음, 자바스크립트 함수로 배열을 정렬시켜 목록을 렌더링하게 코드를 작성했다. 그런데 위와 같이 하면 페이지네이션을 구현할 때 상당히 귀찮은 작업을 해야돼서, 애초에 서버에서 정렬된 리스트를 불러오게 바꿨다. react-query를 사용하면 페이지네이션이나 무한 스크롤 등을 쉽게 구현할 수 있다고 한다. ✨ localStorage is not defined 에러 서버 컴포넌트에서 localStorage에 접근하려고 하면 에러가 나온다. 서버에서는 localStorage가 정의되지 않는다. 그런데, 클라이언트 컴포넌트에서도 단순히 localStorage.getItem 으로 접근하면 not defined 에러가 나온다. 왜냐하면, 클라이언트 컴포넌트도 일단! 서버에서 먼저 렌더링 되기 때문이다. 그리고 나서 hydration 된다. > hydration: 정적 html을 로드하고나서, js, react를 적용해 interactive한 요소들로 채워지는 과정 그러면 어떻게 localStorage 쓰는데? ts const getSavedNotice = : Notice => { if { const storedNotices = sessionStorage.getItem; return storedNotices ? JSON.parse : ; } return ; }; 서버에서 렌더링 될때는 if문 내에 있는 코드를 실행하지 않는다. 하지만 서버에서 빈 배열을 이용해 렌더링 되고, 이후 클라이언트에서 localStorage에서 가져온 배열로 렌더링 하게 되어 서버에서 보여주는 데이터와 클라이언트에서 보여주는 데이터가 서로 다른 경우 가 되어버렸다. content did not match 오류도 보이게 될 것이다. 해결방법 useEffect를 이용해 서버에서 렌더링을 하지 않게 해서 오류를 해결했다. 하지만 불필요한 렌더링을 발생시켜 성능에 악영향을 미칠 수 있을지도? next/dynamic Skipping SSR tsx 'use client'; import dynamic from 'next/dynamic'; type Props = { children: JSX.Element }; const NoSSRRendering = => { return children; }; export default dynamic => Promise.resolve, { ssr: false, }); tsx nextjs 공식 문서에 나와있는 SSR에서 pre-rendering을 하고 싶지 않을 때 사용하는 방법이다. 이를 이용하면 static HTML을 보여주는 단계에서는 rendering 되지 않다가, hydration 후에 렌더링 될 것이다.
bash Error: ENOENT: no such file or directory, stat '/Users/siyori/Desktop/Party-Time-Job/.next/cache/webpack/client-development/3.pack.gz' { errno: -2, code: 'ENOENT', syscall: 'stat', path: '/Users/siyori/Desktop/Party-Time-Job/.next/cache/webpack/client-development/3.pack.gz' } Error: ENOENT: no such file or directory, stat '/Users/siyori/Desktop/Party-Time-Job/.next/cache/webpack/client-development/3.pack.gz' { errno: -2, code: 'ENOENT', syscall: 'stat', path: '/Users/siyori/Desktop/Party-Time-Job/.next/cache/webpack/client-development/3.pack.gz' } ⨯ unhandledRejection: Error: ENOENT: no such file or directory, stat '/Users/siyori/Desktop/Party-Time-Job/.next/cache/webpack/client-development/3.pack.gz' { errno: -2, code: 'ENOENT', syscall: 'stat', path: '/Users/siyori/Desktop/Party-Time-Job/.next/cache/webpack/client-development/3.pack.gz' } ⨯ unhandledRejection: Error: ENOENT: no such file or directory, stat '/Users/siyori/Desktop/Party-Time-Job/.next/cache/webpack/client-development/3.pack.gz' { errno: -2, code: 'ENOENT', syscall: 'stat', path: '/Users/siyori/Desktop/Party-Time-Job/.next/cache/webpack/client-development/3.pack.gz' } webpack.cache.PackFileCacheStrategy Caching failed for pack: Error: ENOENT: no such file or directory, stat '/Users/siyori/Desktop/Party-Time-Job/.next/cache/webpack/client-development/3.pack.gz' 이거 무슨오류인지 모르겠다. 일단 .next 폴더 지우고 다시 빌드해봄. 일시적으로 오류가 나오지 않으나 계속 작업하다가 어느순간 보면 다시 오류가 나온다.
03.11~03.12 ✨ 공고 페이지 UI tailwind는 작성하기에는 쉬운데.. 뭔가뭔가 아쉽다는 생각이 자꾸 든다. tsx 이렇게 css파일을 따로 만들지 않고 바로 작성하고 줄임말로 작성하니 쉬운것 같다고 생각했는데, className에 한줄로 쭉 작성하니 코드가 길어질수록 가독성이 급격히 떨어지는 것 같다. 특히 반응형 디자인 할 때 화면 너비에 따라 스타일이 다르면 코드가 급격하게 길어진다. 저런 태그들이 몇개씩 있다고 생각하면 생각만해도 눈알이 빠질듯? 아직 tailwind를 사용한지 얼마 되지 않아서 그 진가를 내가 못알아보는 걸수도 있다. 근데 지금 생각으로는 앞으로 사용 잘 안할듯 싶다. !image 어찌저찌 반응형 까지 구현했음. ✨ FSD 폴더구조 ? > FSD 공식문서번역 블로그 음,, 회사에서 진행하는 거대 프로젝트에는 분명 체계적이고 철학이 명확한 FSD가 어울린다. 하지만 기간이 짧거나 사이즈가 작은 프로젝트에는 쓰지 않는것이 명확해 보인다. 도메인이 묻으면 무슨폴더, 그 폴더를 또 ui, model 뭐뭐로 또 나누고,, 짧은 프로젝트는 이 파일이 무슨 폴더에 들어가야 하는지 고민하는 시간은 의미없는 시간같다. 최대한 명확하고 단순하게 3개의 파일정도로 나누는 것이 좋아보인다. 프론트엔드에는 뭔가 국룰처럼 여겨지는 폴더구조가 없다고 들었다. 검색을 해봐도, 여러 멘토님께 여쭤봐도 다양한 폴더구조를 듣게 된다. 그때그때 상황에 맞게 적절한 폴더구조를 만드는 것이 중요해 보인다. 이런 폴더구조도 있구나 공부해서 좋긴 하다.
03.09 ~ 03.10 프로젝트 2, 3일차 ✨ User Flow !userflow 먼저 내가 맡은 페이지의 user flow를 그려봤다. !todo github에서 제공하는 project 일정관리를 이용해 To-do list를 팀원별로 등록했다. ✨ Tailwind Taliwind를 처음써봐서 클래스 네임을 하나하나 공식문서에서 찾아 쓰고있다. 하지만 자주 쓰는 요소는 정해져 있으니 익숙해지면 저절로 외워져 작성 속도는 훨씬 빨라질 것 같다! 그리고 반응형을 구현하기에 편리한 것 같다. tsx md: 는 @media { ... } 이다. 다른 사이트 개발자도구에서 본 이런 이상한 클래스 네임이 바로 tailwind였군. !image
03.08 중급 프로젝트 시작! ✨ 프로젝트 정보 이름 : 더 줄게 - 나중에 변경하기로 함. 소개 : 급하게 일손이 필요한 자리에 더 많은 시급을 제공해서 아르바이트생을 구할 수 있는 서비스. 약간 알바천국 같은 느낌. ✨ 회의 내용 각종 컨벤션과 역할 분담을 했다. 1. 협업 툴 깃헙과 노션 슬랙 등등등 많은 툴을 왔다갔다 하면서 관리하는 것이 집중이 안된다는 의견이 있어서, - 깃헙 : 일정관리, PR, Issue - 디스코드 : 상시 소통 - 노션 : 프로젝트 마무리 후 문서 총 정리 이렇게 3개를 사용하기로 했다. 알고보니 깃헙에 위키, 일정관리 칸반보드 등 많은 기능이 있었다. 그 기능들을 적극 이용하기로 결정! 2. 컨벤션 각종 네이밍, PR 규칙 등을 정했다. 2-1. 네이밍 컨벤션 1. 줄임말을 쓰지 않는다. btn button 2. 변수, 함수 명은 길어져도 직관적이게 작성한다. 3. 모든 함수는 표현식으로 사용한다. | 종류 | 방식 | | -- | -- | | 변수명 | camelCaseex.userName="영희"| | 상수명 | UPPER_CASEex.REGULAR_SIZE="상수"| | 클래스명 | tailwind 사용 ex. | 파일명, 폴더명 | PascalCase ex. PascalCase.tsx | 함수 네이밍 패턴 | A/HC/LC 패턴 | 컴포넌트명 | PascalCase 표현식 사용ex. const Input = => | | 유틸 함수명 | camelCaseex. getData = => {}| | 유틸 함수 파일명 | camelCaseex. getData.ts| | 확장자명 | .tsx, .ts 2-2. 커밋 컨벤션 | 타입 | 용도 | | -- | -- | | feat | 새로운 기능 추가 | fix | 버그 수정 | refactor | 코드 리팩토링 | style | 코드 포맷, 스타일 변경 | chore | 작업 관리, 설정 변경 | remove | 파일 삭제 | docs | 문서 관련 작업 | comment | 주석 추가 작업 - type 뒤 띄어쓰기 없이 콜론, 콜론 뒤에는 띄어쓰기를 한다. - 문장은 동사형 명사로 끝낸다. ex. 수정, 추가 - 특수 문자는 사용하지 않는다. - type은 영어 소문자로, subject 텍스트는 한글로 작성한다. 3. Git 전략 개발 기간이 2주로 짧고, 인원이 4명이고 익숙하다는 의견이 있어 Github flow 전략을 사용하기로 했다. | 브랜치 | 역할 규칙 | | -- | -- | | main | 배포 사용자들에게 배포하는 버전을 관리한다. develop 브랜치에서 merge한다. | | develop | 공동 개발 기능, 버그 수정 등 주요 기록들이 모여있다. feature 브랜치에서 merge한다. | | feature | 기능 구현 이슈 별로 브랜치를 생성한다. 브랜치명은 feature-{기능이름}-{상세내용} merge 후에 자동 삭제된다. | 4. CSS CSS-in-Js, CSS module 방식도 고려하였으나 새로운 방식을 경험해 보자는 의견을 반영해 Tailwind로 결정했다. Tailwind의 장단점은 다음과 같다. 장점 - 미리 정의된 클래스로 쉽고 빠르게 작성할 수 있다. - 일관되고 유연하게 작성할 수 있다. 단점 - 초기 러닝커브가 높다. - 클래스를 많이 사용하는 경우 코드가 길고 복잡해 보일 수 있다. - 모든 프로젝트에 적합하지는 않다. 빠르고 간단해야 하는 프로젝트에 적합하다. 5. 폴더 구조 멘토링 시간에 멘토님께서 설명해주신 FSD 폴더 구조에 도전 해보기로 했다. 5-1. 기능 분할 설계 !fsd > FSD 공식문서번역 블로그 6. 기타 규칙 6-1. Export 규칙 1. 하단에 몰아서 하지 않고, export를 함수 바로 옆에 적기 2. 하나만 export 해야 하는 경우, export default를 쓴다. 6-2. Pull Request 규칙 1. PR은 템플릿에 맞게 내용을 작성해서 요청한다. 2. Assignees, Reviewers, Label, milestone을 모두 선택한다. 3. PR 시간대는 자유롭게 올리되, 상황에 맞춰서 어프로브를 요청한다. 6-3. Merge 규칙 1. Approve가 2명 이상일 때 merge 한다. 6-4. 코드리뷰 규칙 1. 코드 리뷰는 필수는 아니다. 2. 단, PR 요청자는 JsDocs를 필수로 작성해주고, 리뷰어는 JsDocs를 꼭 읽어본다. 3. PR 요청자는 상황에 맞게 리뷰어가 꼭 확인해야 하는 부분 항목을 작성한다. 4. 리뷰어는 PR 요청에 꼭 확인해야 하는 부분이 작성되어 있다면 필수로 코드리뷰를 진행한다. ✨ 종합 내가 맡게된 부분은 공고 리스트 페이지, 공고 상세 페이지와 관련 기능들이다. 개발을 하면서 오류와 해결방법, 새롭게 알게 된 부분들을 저장해 놓을 것이다!!!!
굳굳
안녕하세요?