Conversation
init: 프로젝트 초기화, styled 컴포넌트 사용 설정
build: gitignore에 node_modules 추가
build: RTK 로직 추가, 예제 코드 삽입
[Feature/post detail] 포스트 상세 페이지, 에디터 페이지
…sign Feature: main page and design
Feature: 일부 style 및 data 수정
9yujin
left a comment
There was a problem hiding this comment.
안녕하세요, 코드리뷰파트너 한규진입니다!!
저희는 처음엔 원래 하던 방식대로 typesafe-actions를 사용하려 했는데, 구글에 있는 예제들은 모두 rtk를 사용하더군요. createStore가 deprecated라고 나올때부터 알아봤어야 했습니다..ㅠㅠ
그 과정에서 처음 rtk를 공부하면서, 헬시어 코드에서 세팅해두신 코드들을 보고 많은 도움이 되었습니다. 아직 nextjs에서 리덕스를 사용하는 과정이 많이 어려워서 도움이 되는 리뷰는 드리지 못한것 같네요ㅠㅠ 앞으로 더 공부해보겠습니다!!
덕분에 많이 알아가는 것 같습니다. 그럼 스터디 시간때 봬요ㅎㅎ
| extraReducers: { | ||
| [HYDRATE]: (state, action) => { | ||
| return { | ||
| ...action.payload, | ||
| ...state, | ||
| }; | ||
| }, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
저희도 nextjs에 리덕스를 사용하면서 제일 많이 고민했던 부분입니다. 저흰 다른 예제들에서 쉽게 볼 수 있는 rootReducer에서 hydrate하는 방법을 사용했었는데, postSlice 안에서 extraReducers를 사용하시는것을 보고 많이 찾아봤습니다.
이 블로그를 보면 hydrate시 전체 스토어가 아닌 특정 스토어에서만 hydrate가 되도록 하는 방법이라고 하는데, 헬시어팀에서도 저 이유로 코드를 저렇게 작성하신걸까요..?
hydrate가 생길때 스토어에 새로운 갈래가 생기고, post를 업데이트하거나 삭제할땐 새로 생긴 갈래가 아닌 기존의 상태가 계속 바뀌는것을 확인할 수 있었습니다.
이 부분이 PR 메세지에서 말씀해주신 클라이언트state로 따로 관리하는 '꼼수'인 것 같은데, 정확히 어떤 방식으로 이루어지는지 코드를 봐도 잘 이해하기가 어렵습니다ㅠㅠ 혹시 어떤 과정인지 알려주시면 감사하겠습니다!!
There was a problem hiding this comment.
제가 시험이 있었어서, 뒤늦게 지금까지 알아본 내용들 정리해서 올려드립니다. 우선, 이미지로 첨부주신 내용은 단순히 제 실수입니다. HYDRATE를 통해 server state를 통합할 때 '...action.payload.post'로 적어야 할 내용을 '...action.payload'라고 적어서 이상한 구조가 만들어졌네요.
클라이언트 state와 서버 state에 관하여
제가 사용한 꼼수는 props를 검사해 true가 반환되면 (다시말해 server props가 로드된 상태라면) props를 useSelector를 이용해 client state props로 대체하는 방법입니다. 조금 더 직관적으로 보기 위해 다음을 참조해주세요.
export default connect(mapStateToProps)(Detail);
function mapStateToProps(state, ownProps) {
console.log(ownProps.posts);
return { posts: state.post.posts };
}
위 코드는 react-redux 패키지에서 지시하는 mapStateToProps를 사용하고 있습니다. 본래는 전역 상태를 컴포넌트 prop으로 전달하는 함수입니다. 이때 ownProps는 컴포넌트에 <Component props={item}/> 식으로 전달된 props를 가져오는 매개변수입니다. 이후 컴포넌트와 mapStateToProps를 connect를 통해 묶고 있습니다.
이대로 코드를 실행하면 컴포넌트에 아무 인자를 전달하지 않았음에도 정상적인 출력이 발생하는 것을 확인할 수 있습니다. 다시말해, 서버로부터 제공받은 props, getStaticProps를 통해 컴포넌트로 연결된 서버 state입니다.
그렇다면 서버 state는 무엇일까요? 저도 이 부분에서 많이 헤맸습니다. SSG는 서버에 로드된 웹페이지를 렌더링해서 클라이언트에 보내주는 방법입니다. 따라서 SSG를 통해 렌더링된 페이지는 반드시 html 생성 당시 state를 가지고 있습니다. 서버 state란 이 state를 말합니다.
이때 두가지 특징이 발생하는 것을 알 수 있었는데요. 우선, getStaticProps 함수 내에서 아무리 store를 조회해봐야 초기 state 상태만 출력됩니다. getStaticProps에서 클라이언트 state와 관련해 할 수 있는 작업은 서버 state를 클라이언트 state에 dispatch를 이용해 보내주는 것 밖에 없습니다. (이후 Hydrate를 통해 이 둘을 적절히 통합하는 로직을 작성해야 합니다.)
또한, getStaticProps에서는 아무리 콘솔을 찍어도 콘솔에 아무 내용이 나타나지 않습니다. getStaticProps는 클라이언트(브라우저)에서 실행되는 함수가 아니라 nodejs 서버에서 실행되는 함수더라구요.
문제 해결에 관하여
포켓돈의 프로젝트도 저희와 같이 getStaticProps를 사용중이신데, 따라서 해당 url에 연결될 때 마다 서버 state (초기 상태)를 요청하기 때문에 과거 상태로 롤백 되시는 것 같아요. 마찬가지로 StaticPath로 경로를 미리 생성하고 있어 새로운 글의 조회도 안되고 있습니다.
저희처럼 위의 props를 조회해 boolean을 토대로 결정하는 방식 또는 다음의 예제와 같이 mapStateProps를 통해 staticProps로 불러온 props를 클라이언트 props를 덮어 씌우는 방식을 하면 될 것 같습니다. 저번에 이미 첨부했지만..
사용자 상호작용이 필요한 이번 프로젝트 목적에 가장 부합하는 방식은 SSR + 클라이언트 state 업데이트가 발생할 때 마다 이러한 요청을 처리하고 업데이트해서 다시 보내줄 서버가 필요한 방식일 것 같습니다. 서버 없이 사용하려고 하니 여러 제약 조건이 발생하는 것 같아요.
추가로, 알아보면서 redux-wrapper에 대한 불만이 정말 많더라고요. Vercel에서 제공하는 화려한 SWR이라는 상태관리 기법도 있던데 함께 공부해보면 좋을 것 같아요. 위의 내용은 어떤 문서를 참조하지 않고 제가 여기저기 뒤지면서 정리한거라 오류가 있을 수 있습니다!
|
|
||
| export default Edit; | ||
|
|
||
| export const getStaticProps: GetStaticProps = wrapper.getStaticProps( |
| debug: process.env.NODE_ENV !== "production", | ||
| }); | ||
|
|
||
| export function getAllPostIds() { |
There was a problem hiding this comment.
페이지에서 따로 store를 임포트하지 않아도 함수만 불러와서 쓸수 있겠군요!!
| }); | ||
|
|
||
| const storePosts = useAppSelector((state) => state.post.posts); | ||
| posts = posts ? storePosts : posts; |
There was a problem hiding this comment.
이 부분도 조금더 공부해보겠습니다,,
더 찾아보니 보통은 redux-persist를 이용해서, store가 초기화된 이후에도 그대로 유지될수 있도록 하는 방법을 쓴다고 하네요!!



안녕하세요. Healthier입니다😄 저희 모두 프론트끼리의 협업이 처음이라 낯설었지만 기한 내에 무사히 해낸 것 같아서 뿌듯하네요. 이번 과제를 시작으로 앞으로의 팀 프로젝트도 열심히 해보겠습니다!
배포링크
https://react-blog-15th.vercel.app/
Key Questions
Next.js를 사용하는 이유
Next.js는 React 기반의 프레임워크로 Server-Side Rendering을 제공합니다.
React의 경우 Client-Side Rendering을 사용하는데요, 이러한 방법은 위와 같은 단점을 가질 수 있습니다. 웹사이트를 요청했을 때 빈 html을 가져오고 script를 로딩하게 되어 로딩 시 첫 화면에서 빈 화면이 나오게 되고 SEO에도 적합하지 않습니다.
Next.js는 React에서 제공하는 Client-Side Rendering의 단점을 해결할 수 있습니다.
Next.js는 Server-Side Rendering을 제공하기 때문에 pre-rendering으로 데이터가 렌더링된 페이지를 가져올 수 있어 첫 로딩시 빈 화면을 제공하지 않고 검색 엔진에 잘 노출될 수 있습니다.
또한 Next.js는 파일 기반 라우팅 시스템을 제공합니다.
Next.js 프로젝트 생성시 pages 폴더가 자동생성됩니다. 해당 폴더에 원하는 라우팅 페이지를 넣어주면 파일 이름에 해당하는 route를 가지게 되고 간단하게 routing을 사용할 수 있습니다. pages 폴더 내에서 폴더를 선언하게 되면
/폴더명/폴더명/.../파일명의 방식으로 route를 가지게 됩니다.또한 route를 동적으로 구현하고 싶을때는 []를 사용해 상세페이지의 변수명을 사용할 수 있습니다. 저희 프로젝트를 예시로 보시면
pages/detail/[id],pages/edit/[id]를 사용해 동적인 route를 사용하고 있습니다.따라서 다음과 같이 웹페이지에서
detail/[고유id],edit/[고유id]를 경로로 사용할 수 있습니다.결국 Next.js는 react-router를 사용해 코드 기반 라우팅을 제공했던 React와 달리 직관적인 방식으로 파일 기반 라우팅을 제공해 편리함을 제공합니다.
참고 https://velog.io/@syoung125/Next.js-기본-개념-1-Next.js-란-Next.js를-왜-사용할까-Next.js의-장점은
SEO란?
Search Engine Optimization의 약자인 SEO는 구글 (혹은 다른 검색엔진)의 검색 결과 상 사이트가 상단에 노출되도록 하는 전략을 말합니다. 현재 구글은 Google Quality Guideline 을 통해 검색엔진 상단에 노출시키 위한 일련의 가이드라인을 제공하고 있습니다. 위의 가이드라인을 위반하는 사이트는 구글에 의해 패널티를 받을 수 있습니다. 구글의 랭킹 알고리즘은 매년 주요 업데이트를 진행하며 이러한 업데이트를 추적하는 것도 중요한 개발자의 작업입니다.
SEO는 개발자만의 작업이 아닙니다. 웹페이지가 제공하는 정보의 질과 신뢰성이 가장 중요합니다. 개발자는 사용자와 크롤러에 의한 페이지 접근성을 높이는 작업을 진행할 수 있습니다. 올바른 html 태그의 사용, 데이터의 구조화, 모바일 친화적인 페이지와 같은 작업들이 존재합니다. 일례로 구글은 <title> 태그 안의 내용을 검색결과의 제목으로 사용합니다. 따라서 <title> 태그를 잘 사용하고 해당 태그 내 적절한 내용을 적어넣는 것은 웹페이지의 노출에 중요할 수 있습니다.
구글은 SEO를 위해 Google Search Console을 제공하고 있습니다. 혹은 크롤링을 수행해 문제점을 보여주는 상업적 툴을 이용해 SEO를 시도할 수 있습니다.
기본적인 검색엔진은 서버에 의해 생성된 html 페이지를 분석합니다. 지난 시간 동안 크롤러는 다량의 페이지를 빠르게 탐색해야 하므로 자바스크립트를 지원하지 않았습니다. 만일 어떤 페이지가 CSR을 통해 렌더링한다면 크롤러는 그냥 지나칩니다. 따라서 이커머스 사이트의 경우 서버에 인덱싱용 정적 페이지를 생성하고 로드과 완료되면 CSR이 개입해 사용자 경험을 강화해왔습니다. 이러한 개념을 토대로 발전한 것이 SSR입니다.
다만, 현재는 CSR로 생성된 페이지를 크롤러가 지원하기 시작했습니다. 따라서, 모던 웹의 발전에 따라 SSR의 중요성은 점차 감소할 수도 있습니다.
검색엔진 최적화 만큼 중요한 것은 웹페이지 속도 개선입니다. 이는 Google PageSpeed Insights를 통해 간편하게 조회할 수 있습니다. Mobify는 100ms의 홈페이지 속도 차이 개선으로 세션 기반 전환율의 1.11% 증가 및 연간 $380,000의 수익 증가를 이끌어 냈습니다. 홈페이지 속도를 측정하는 대표적인 지표는 다음과 같습니다. (1) FID: First Input Display (2) FCP: First Contentful Paint (3) TTI: Time to Interactive. 자세한 내용은 다음의 링크를 통해 찾을 수 있습니다. 웹페이지의 속도는 제한된 대역폭 내에서 얼마나 많은 자원을 불러오는지에 영향을 받습니다. 여기에는 폰트, 서드파티 스트립트, 이미지 등 자원이 해당됩니다.
출처
https://medium.com/welldone-software/seo-for-developers-a-quick-overview-5b5b7ce34679
https://business.adobe.com/blog/basics/server-side-vs-client-side-rendering-and-changing-seo-practices
성능 최적화를 위해 사용한 방법
Next.js는 SSR과 SSG를 지원하는 리엑트 배포 프레임워크입니다. 이번 과제가 서버와 통신을 요구하지는 않지만, Next.js에서 제공하는 기초적인 기능들을 사용하기 위해 노력했습니다. 이중 성능 최적화와 관련된 부분은 SSG(Static Site Generation)의 사용입니다.
/경로에 위치한 메인 페이지를 제외하고/edit/및/detail/경로에 위치한 페이지들은 SSG를 통해 생성되고 브라우저로 전달됩니다. 따라서 각 페이지는 컴파일 타임에 생성되어 서버에 저장되어 있다가 요청이 발생하면 html 및 리소스로 브라우저에 전달됩니다.다만, 이럴 경우 수정하거나 새로 생성한 글이 브라우저에 나타나지 않는 문제가 존재했습니다. SSG를 사용하므로 Redux 전역 상태로 새로 작성한 글을 저장하더라도, 해당 경로에 들어가면 빌드 타임에 생성된 초기 상태가 나타납니다. 따라서, 서버로부터 조회하는 함수인 GetStaticProps의 중간에 개입해 Redux 전역 상태의 값을 삽입하고자 했습니다.
이 과정에서 Next.js와 Redux를 연결하는 Redux Wrapper를 사용했는데, 서버와 클라이언트가 서로 독립적인 State를 가지고 있었습니다. 또한, 서버 State는 Node.js를 통해 독립적으로 관리되고 있으므로 클라이언트 State에 접근할 수 없다는 문제를 발견할 수 있었습니다. 따라서 중간에 클라이언트 State를 삽입하는 시도는 실패했습니다.
따라서, 두번째 방법으로 초기 렌더링만 SSG를 이용하고 이후 변경이 발생하면 Client State를 참조하는 방식으로 성공할 수 있었습니다. 비유하자면 껍질만 서버로 불러온 뒤 내용은 로컬에서 업데이트하는 방식입니다. 이는 다음의 예제에서 아이디어를 얻었습니다. 예제는 SSG로 우선 서버에서 페이지를 생성한 뒤, 이후 상태변화를 클라이언트 State로 관리하고 있습니다. 우연히 발견한 꼼수를 통한 우아하지 않은(ㅠ) 방법이지만, 구현에 의미를 둘 수 있을 것 입니다.
전반적인 협업 과정
구현할 페이지를 기준으로 개발할 파트를 분담했습니다.
대헌 : 메인페이지, 상태관리
효정 : 상세페이지, 에디터페이지
각자 기능에 따른 branch에서 작업한 후 pull request를 날리고 Merge하는 방식으로 협업을 진행했습니다.
감사합니다!