Blog

April 5, 2023

개인 용도의 블로깅, 포르폴리오 게시를 위한 블로그. 게시물 게시와 관리 기능 구현.

이번 프로젝트는 지금 이 페이지를 담고 있는 웹을 만드는 프로젝트로써 포트폴리오와 블로그 기능을 수행 할 수 있는 개인 웹사이트를 리액트를 사용하여 만드는 일이다. 이곳에 개발과 공부를 하면서 얻은 결과물과 노트를 담는다. 이를 위해 리액트 외에 클라이언트 쪽의 전역 상태를 관리하기 위해 Redux를 사용하였으며 서버쪽 비동기 데이터를 관리하기 위해 React-Query를 사용하으며 서버는 firebase를 통해 구축했다. 사실 블로그는 정적인 페이지 위주로 구성하는 것이 성능면에서 좋겠지만, 이번 프로젝트에서는 스택들을 연습하고 서버와 연동되는 CRUD기능을 구현하는 연습을 하고자 한다.

Stacks

React & typescript : 프로젝트 구성.

Redux : Client-side 전역 상태를 관리한다.

React-Query: Server-side 비동기 상태를 관리한다.

firebase: 서버 구축

프로젝트 구성

해당 프로젝트는 Home, Posts, 그리고 Projects 페이지로 구성되어 있으며 각 이름에 맞게 Home 페이지에는 웹의 개요, Posts 페이지에는 포스트를 모아 블로그 기능을 하며 Projects 페이지에는 포트폴리오를 구성해 놓았다. 또한 admin권한을 얻으면 Edit 페이지가 추가되어 웹의 컨텐츠를 추가,수정,삭제할 수 있으며 이 데이터들은 firebase에 저장되어 서버 안에서 관리된다.

Header component

Scroll Header로 사용자 스크롤에 반응하여 나타나며 숨는다. 로그인 기능을 구현해, 관리자로 로그인 시 게시물을 작성, 수정, 삭제 등 관리할 수 있다.

Home page

블로그에 대한 간단한 소개글과 블로그에 담긴 컨텐츠를 간략하게 나타낸다.

Posts page

사용자가 블로그 내용을 볼 수 있으며, 필터링 기능을 추가하여 프로젝트 목록을 동적으로 렌더링하여 사용자가 관심 있는 주체를 표시한다. 각 Post Card는 글의 제목, 작성날짜, 간략한 설명을 제공하며 유저는 해당 카드를 클릭하여 해당 포스트에 대한 상세 페이지로 이동할 수 있다. 관리자 권한을 얻은 사용자의 경우 해당 포스트를 수정, 삭제 할 수 있다.

Projects page

Posts page와 마찬가지로 각 Project card는 프로젝트의 간략한 정모가 담겨 있으며 유저는 해당 가드를 클릭하여 상세페이지로 이동할 수 있다.

Edit page

관리자로 로그인된 사용자는 게시물을 작성, 수정, 삭제할 수 있다.

주요기능

반응형 웹 디자인 : 반응형 디자인으로 구성된 블로그 프로젝트는 모바일, 태블릿, 데스크톱 등 다영한 디바이스에서 최적화된 사용자 경험을 제공하고자 노력하였다.

다크모드 : 사용자가 웹의 테마를 전활 할 수 있도록 기능 구현. Tailwindcss와 Redux를 이용해 관리하며 React-helmet-async를 통해 html 태그를 조작하여 구현.

Client side 상태 관리를 위한 Redux 사용: 웹의 주요 데이터라고 할 수 있는 post와 project에 관한 데이터를 이미 react-query에서 관리하고 있기 때문에 해당 규모에서 Redux의 관리가 필요한 전역 상태는 거의 없다. 관리할 전역상태가 많지 않음에도 리덕스의 Reducer방식은 상태 관리를 명료하게 해주는 이점이 있었다.

서버 데이터 관리를 위한 React-query사용: 서버에 와의 통신에서 최신데이터를 관리하고 불필요한 데이터 요청을 막을 수 있다. 이번 프로젝트에서는 컴포넌트 간 데이터 동기화에 많은 이점을 보았다.

Details

프로젝트 구성

각 페이지를 먼저 구성하고 그에 필요한 요소들을 컴포넌트로 만들었으며 로직이 커지는 특정 페이지나 컴포넌트는 container를 구성하여 컨테이너에서 비지니스 로직을 처리하고 컴포넌트에서 렌더링을 하는 방식으로 만들었다.

📦src ┣ 📂components ┃ ┣ 📜Button.tsx ┃ ┣ 📜Footer.tsx ┃ ┣ 📜Header.tsx ┃ ┣ 📜HeaderScroll.tsx ┃ ┣ 📜Icon.tsx ┃ ┣ 📜Login.tsx ┃ ┣ 📜Modal.tsx ┃ ┣ 📜Nav.tsx ┃ ┣ 📜PostCard.tsx ┃ ┣ 📜ProjectCard.tsx ┃ ┗ 📜Tag.tsx ┣ 📂containers ┃ ┣ 📜EditPostContainer.tsx ┃ ┣ 📜EditProjectContainer.tsx ┃ ┣ 📜PostContainer.tsx ┃ ┗ 📜PostsContainer.tsx ┣ 📂hooks ┃ ┣ 📜usePost.ts ┃ ┗ 📜useProject.ts ┣ 📂pages ┃ ┣ 📜EditPost.tsx ┃ ┣ 📜EditProject.tsx ┃ ┣ 📜Home.tsx ┃ ┣ 📜Post.tsx ┃ ┣ 📜Posts.tsx ┃ ┣ 📜Project.tsx ┃ ┗ 📜Projects.tsx ┣ 📂redux ┃ ┣ 📂slices ┃ ┃ ┣ 📜darkModeSlice.ts ┃ ┃ ┗ 📜userSlice.ts ┃ ┗ 📜store.ts ┣ 📂svgs ┃ ┣ 📜firebase.svg ┃ ┣ 📜query.svg ┃ ┣ 📜react.svg ┃ ┣ 📜redux.svg ┃ ┗ 📜ts.svg ┣ 📂utils ┃ ┣ 📜Firebase.ts ┃ ┗ 📜functions.ts ┣ 📜App.css ┣ 📜App.tsx ┣ 📜defaultvalues.ts ┣ 📜index.css ┣ 📜index.tsx ┣ 📜react-app-env.d.ts ┗ 📜type.d.ts

styling

Tailwindcss를 이용했다. 자유도는 조금 떨어지지만 색감이나 화면과 컴포넌트 간 스케일을 조절하는데 많이 도움이 된다. hover:, focus:등을 이용하여 동적인 스타일링이도 가능하여 dark:를 이용하여 다크모드도 쉽게 구현할 수 있었다. 또한 sm:, md:, lg:등을 이용해 스크린 크기에 비래에 동적 스타일링도 지원하여 미디어쿼리도 간편하게 구현할 수 있었다. Tailwindcss를 이용한다 하더라도 디자인적 요소를 모두 구성하기엔 능력도 시간도 부족하므로 참고 사이트를 클론하였다.

상태 관리 툴

이 프로젝트에서는 전역 상태를 client와 server, 둘로 나눠 관리 했다. react-query 하나로 상태관리를 할 수도 있었지만 이는 react-query의 용도와도 맞지 않을 뿐더러 앞으로 해당 프로젝트가 개인적 용도로 쓰는 웹이지만 결국 앞으로의 프로젝트 규모를 염두해야 하므로 스택을 구분하여 사용할 필요가 있다고 생각했기 때문이다. 상태 관리툴은 여러가지가 있고 관리해야할 전역 상태가 많지 않으므로 리액트에서 기본으로 제공하는 상태관리 툴인 Context Api가 적당할 것이다. 해당 프로젝트의 복잡성이 높지 않고 보일러 플레이트도 적기 떄문이다. 하지만 리덕스를 사용하면서 상태관리를 이해하고 전역상태의 보호성도 높이면서 실수를 최소화 할수 있다.

export default configureStore({ reducer: { user: userReducer, // filter: filterReducer, darkMode: darkModeReducer, }, });

Custom hooks 사용

비동기 데이터를 가지고 오거나 수정, 삭제 함에 있어 컴포넌트 간 동기화가 제대로 되지 않아 데이터가 혼란스럽게 퍼져있는 느낌을 받아 프로젝트가 전반적으로 산만한 느낌을 주었다. 더욱이 react-query는 key를 통해 데이터 fetching을 결정하기 때문에 fetching로직이 컴포넌트 이곳저곳에 퍼져있으면 유지보수에도 좋지 않을뿐더러 프로젝트를 이해하기에 어려움이 따른다. 이떄문에 fetching로직을 한곳에서 관리할 필요성이 있었다. 이에 custom hook인 useProjectusePost를 만들어 각 데이터를 관리하는 훅을 하나로 만들어 필요한 컴포넌드에서 불러와 사용하였다.

export default function useProject(id?: string) { const queryClient = useQueryClient(); const projectQuery = useQuery(["projects"], () => getItems("projects"), { staleTime: 1000 * 6 * 5, }); const getProject = useQuery(["projects", id], () => getItem(id, "projects"), { staleTime: 1000 * 60 * 5, }); /////// /////// return { projectQuery, getProject, addProject, updateProject, removeProject }; }

firebase 서버

백엔드 스택이 없어서 서버 구축에 어려움이 있으므로 플렛폼을 사용하기로 했다. 파이어베이스 기능중에는 admin권한을 얻기위한 인증 기능과 데이터베이스만 사용했다.