월 17,600원
5개월 할부 시다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
리액트 리랜더링의 과정에 관한 질문
안녕하세요, 강의 잘 듣고 있습니다. 리액트 노드버드 sns 강좌 중 "리랜더링 이해하기" 편을 보면서 애매하게 이해하고 있는 부분이 있어 확실하게 하고자 질문을 남깁니다. 크게 두가지 질문인데요, 1. 이 사진은 강의 중 로그인 컴포넌트를 만드는 코드입니다. class명이 'number2'인 태그에 loading이라는 prop을 전달하고 있습니다. 나중에 로그인 정보를 입력하고 이 로그인 버튼을 눌렀을때 axios와 같은 방법으로 서버에 요청을 하고 그 결과값을 받는 동안 loading props를 true로 바꾸는 식으로 진행이 되겠죠? 제가 궁금한거는 이때 prop이 바뀌는 부분은 'number2' 이 부분이니까 정확이 이 부분만 리랜더링이 되는건가요? 아니면 이것을 감싸고 있는 ButtoWrapper(class명 'number1') 이 부분 전체가 리랜더링이 되는걸까요? 2. 리액트 툴을 통해서 리랜더링 되는 부분을 이렇게 색깔로 확인할 수가 있는데, 여기서 색깔로 표시되는 부분은 정확하게 리랜더링이 되고 있다는 의미일까요? 아니면 이 중에 리랜더링이 안되고 있는데 버그처럼 계속 반짝이고 있는 경우도 있는걸까요? 질문이 허접해서 죄송합니다.. 확실히 알고자 이렇게 질문남깁니다 ㅠ
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
로그인 시 대기중 오류
안녕하세요 제로초님 로그인 시 네트워크탭에서 대기중으로 계속 나오는 문제가 발생했습니다. 다른 질문들을 봤을 때, 관계성과 관련된 코드들에서 오타를 찾고, 테이블을 다시 생성하는 방법을 취해봤는데요. 다시해도 계속 대기중으로 나타나서요 ㅜ 서버 실행 중 Executing (default): CREATE TABLE IF NOT EXISTS `Users` (`id` INTEGER NOT NULL auto_increment , `email` VARCHAR(30) NOT NULL UNIQUE, `nickname` VARCHAR(30) NOT NULL, `password` VARCHAR(100) NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci; Executing (default): SHOW INDEX FROM `Users` FROM `react-nodebird` Executing (default): CREATE TABLE IF NOT EXISTS `Posts` (`id` INTEGER NOT NULL auto_increment , `content` TEXT NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `UserId` INTEGER, `RetweetId` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`UserId`) REFERENCES `Users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, FOREIGN KEY (`RetweetId`) REFERENCES `Posts` (`id`) ON DELETE SET NULL ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; Executing (default): SHOW INDEX FROM `Posts` FROM `react-nodebird` Executing (default): CREATE TABLE IF NOT EXISTS `Comments` (`id` INTEGER NOT NULL auto_increment , `content` TEXT NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `UserId` INTEGER, `PostId` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`UserId`) REFERENCES `Users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, FOREIGN KEY (`PostId`) REFERENCES `Posts` (`id`) ON DELETE SET NULL ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; Executing (default): SHOW INDEX FROM `Comments` FROM `react-nodebird` Executing (default): CREATE TABLE IF NOT EXISTS `Hashtags` (`id` INTEGER NOT NULL auto_increment , `name` VARCHAR(20) NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; Executing (default): SHOW INDEX FROM `Hashtags` FROM `react-nodebird` Executing (default): CREATE TABLE IF NOT EXISTS `Images` (`id` INTEGER NOT NULL auto_increment , `src` VARCHAR(200) NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `PostId` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`PostId`) REFERENCES `Posts` (`id`) ON DELETE SET NULL ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci; Executing (default): SHOW INDEX FROM `Images` FROM `react-nodebird` Executing (default): CREATE TABLE IF NOT EXISTS `PostHashtag` (`createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `PostId` INTEGER , `HashtagId` INTEGER , PRIMARY KEY (`PostId`, `HashtagId`), FOREIGN KEY (`PostId`) REFERENCES `Posts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`HashtagId`) REFERENCES `Hashtags` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; Executing (default): SHOW INDEX FROM `PostHashtag` FROM `react-nodebird` Executing (default): CREATE TABLE IF NOT EXISTS `Like` (`createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `PostId` INTEGER , `UserId` INTEGER , PRIMARY KEY (`PostId`, `UserId`), FOREIGN KEY (`PostId`) REFERENCES `Posts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`UserId`) REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_general_ci; Executing (default): SHOW INDEX FROM `Like` FROM `react-nodebird` Executing (default): CREATE TABLE IF NOT EXISTS `Follow` (`createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `FollowingId` INTEGER , `FollowerId` INTEGER , PRIMARY KEY (`FollowingId`, `FollowerId`), FOREIGN KEY (`FollowingId`) REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`FollowerId`) REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci; Executing (default): SHOW INDEX FROM `Follow` FROM `react-nodebird` db 연결성공 Executing (default): SELECT `id`, `email`, `nickname`, `password`, `createdAt`, `updatedAt` FROM `Users` AS `User` WHERE `User`.`email` = 'test@test.com'; Executing (default): INSERT INTO `Users` (`id`,`email`,`nickname`,`password`,`createdAt`,`updatedAt`) VALUES (DEFAULT,?,?,?,?,?); 터미널에는 위와 같이 나오고 회원가입은 잘 되었습니다. mysql 테이블에서도 데이터가 들어온 것을 확인했습니당. 그런데 로그인을 하면 아래처럼 대기중이다가 시간지나면 저렇게 에러메세지가 나옵니다. 저는 포트번호를 5001로 한 상태입니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
parms를 가져오는 과정
request 객체 안의 params에 postId가 언제 어떻게 담겨지는 건지 궁금합니다. Express 서버에서 요청을 받는 순간에 요청의 엔드포인트를 파싱해서 :postId 부분을 params로 인식하고 request 객체의 params에 초기화해주는건가요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
saga 제너레이터 함수에서 catch를 탔을때
function *addPost(action){ try{ yield delay(2000); const result = yield call(addPostAPI,action.data); yield put({ type:"ADD_POST_SUCCESS", data:result.data }); }catch (err) { yield put({ type:"ADD_POST_FAILURE", data:err.response.data }); } } 이런식으로 addPost 가 있고 실제로 axios 로 네트워크 요청하게하면 오류가 떠서 catch 로 갈수도 있는데 이때 보통 어떤식으로 처리하나요? reducer 에서 initialState 에는 logInError 가 있는데 UserProfile.js에서는 loginError 관련 처리가 없는것같아서요
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
안녕하세요 제로초님 nextjs로 빌더를 만들어볼까 하는데 힌트를 찾지못해 질문드립니다.
안녕하세요 제로초님. 지금 포트폴리오를 만드는김에 빌더까지 확장해서 만들어 볼까 싶은데 힌트를 찾지 못해서 질문 드립니다. 일단 각 게시판 별로 스킨을 만들어서 DB에 스킨명을 저장할 생각입니다. fs으로 스킨명의 js파일을 찾아 저장할 생각인데 문제는 해당 컴포넌트를 어떻게 import해서 가져와서 뿌려주느냐 입니다. 위 스샷처럼 list에 Basic 이라는 리스트 컴포넌트와 Thumbnail이라는 리스트 컴포넌트 2개를 가지고 있습니다. 예를들어서 notice게시판과 photo게시판 2개의 게시판이 있고, db에 게시판 설정 테이블을 만들고 notice 게시판은 리스트 스킨을 Basic으로 저장하고, photo게시판은 Thumbnail로 저장합니다. notice라는 게시판에서는 Basic 컴포넌트를 , photo라는 게시판에서는 Thumbnail컴포넌트를 불러와 사용하게 하려 하는데 제가 node를 다루기 전엔 php만 다뤄봐서 php에선 그냥 단순히 스킨명을 string으로 가져와서 include로 가져왔으면 됐는데 node에서는 어떻게 하면 따로 불러와서 사용할수있는지 궁급합니다. 혹시나 제가 만들려는 접근방식이 틀렸다면, 검색 키워드좀 부탁드리겠습니다. 감사합니다
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
무한정 back으로부터 post 데이터를 불러와요!
안녕하세요 zerocho 선생님... 다른게 아니라 이 부분에서 아주 크나큰 문제에 맞닥뜨려 작업을 못하고 있습니다. pages/index.js에서 useEffect를 사용해서 첫 페이지에 오면 로그인 유무를 판단하고 또 기존에 있던 게시글을 불러와야 하는것을 잘 아는데 문제는 로그인할 경우에는 딱 한번만 LOAD_MY_INFO_REQUEST를 잘 수행하는데 LOAD_POST_REQUEST를하는순간 무한 응답을 합니다. post man에서 받아온 데이터를 다음과 같습니다. 사실 저는 springboot를 back으로 사용하고 있는데 이유는 JPA를 좀 사용해보고 싶어서 였습니다. next.js를 아예 안쓰는것이 나앗던 것 같지만 처음에 강의를 접하고 할때는 아무것도 몰라서... 무진 후회중.. 그냥따라할걸... 하여간... 기존의 데이터도 이런 식으로 받아오는데 문제는 무한 응답을 요청하니까 도대체 어디서 잘못된건지 모르겠다는 것입니다. ㅠㅠ 제발좀 도와주십시오.;.. pages/index.js import { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import AppLayout from "../components/AppLayout"; import PostCard from "../components/middleComponent/Post/PostCard"; import PostForm from "../components/middleComponent/Post/PostForm"; import { LOAD_POST_REQUEST } from "../reducers/post"; import { LOAD_MY_INFO_REQUEST } from "../reducers/user"; const Home = () => { const dispatch = useDispatch(); const { me } = useSelector((state) => state.user); const { mainPosts, hasMorePost, loadPostLoading } = useSelector((state) => state.post); useEffect(() => { console.log("useEffect in index.js") dispatch({ type: LOAD_MY_INFO_REQUEST }); // dispatch({ // type: LOAD_POST_REQUEST, // }); }, [hasMorePost, loadPostLoading]); useEffect(() => { function onScroll() { if (window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300){ if (hasMorePost && !loadPostLoading) { dispatch({ type: LOAD_POST_REQUEST, }); } } } window.addEventListener('scroll', onScroll); return () => { window.removeEventListener('scroll', onScroll); } }, []); console.log(mainPosts); return ( <AppLayout> {me && <PostForm />} {mainPosts.map((post) => <PostCard key={post.id} post={post}/>)} </AppLayout> ); } export default Home; reducers/post.js import shortId from 'shortid'; import produces, { produce } from 'immer'; import faker from 'faker'; import { ConsoleSqlOutlined } from '@ant-design/icons'; export const initialState = { mainPosts: [], imagePaths: [], hasMorePost: true, loadPostLoading: false, loadPostDone: false, loadPostError: null, addPostLoading: false, addPostDone: false, addPostError: null, addCommentLoading: false, addCommentDone: false, addCommentError: null, removePostLoading: false, removePostDone: false, removePostError: null, } export const generateDummyPost = (number) => Array(number).fill().map(() => ({ id: shortId.generate(), User: { id: shortId.generate(), nickname: faker.name.findName(), }, content:faker.lorem.paragraph(), Images: [{ src: faker.image.imageUrl(), }], Comments: [{ User: { id: shortId.generate(), nickname: faker.name.findName() }, content: faker.lorem.sentence(), }], })); export const LOAD_POST_REQUEST = 'LOAD_POST_REQUEST'; export const LOAD_POST_SUCCESS = 'LOAD_POST_SUCCESS'; export const LOAD_POST_FAILURE = 'LOAD_POST_FAILURE'; export const ADD_POST_REQUEST = 'ADD_POST_REQUEST'; export const ADD_POST_SUCCESS = 'ADD_POST_SUCCESS'; export const ADD_POST_FAILURE = 'ADD_POST_FAILURE'; export const ADD_COMMENT_REQUEST = 'ADD_COMMENT_REQUEST'; export const ADD_COMMENT_SUCCESS = 'ADD_COMMENT_SUCCESS'; export const ADD_COMMENT_FAILURE = 'ADD_COMMENT_FAILURE'; export const REMOVE_POST_REQUEST = 'REMOVE_POST_REQUEST'; export const REMOVE_POST_SUCCESS = 'REMOVE_POST_SUCCESS'; export const REMOVE_POST_FAILURE = 'REMOVE_POST_FAILURE'; export const ADD_POST_TO_ME = 'ADD_POST_TO_ME'; export const REMOVE_POST_OF_ME = 'REMOVE_POST_OF_ME'; export const addPost = (data) => ({ type: ADD_POST_REQUEST, data }) export const addComment = (data) => ({ type: ADD_COMMENT_REQUEST, data }) // const dummyPost = (data) => ({ // id: data.id, // content: data.content, // User: { // id: 1, // nickname: 'thelovedaejeon', // }, // Images: [], // Comments: [], // }); // const dummyComment = (data) => ({ // id: shortId.generate(), // content: data, // User: { // id: 1, // nickname: 'thelovedaejeon', // }, // }) //이전 상태를 action을 통해 다음 상태로 만들어 내는 함수 (불변성을 지키면서) const reducer = (state = initialState, action) => { return produce (state, (draft) => { switch (action.type){ case LOAD_POST_REQUEST: draft.loadPostLoading = true; draft.loadPostDone= false; draft.loadPostError= null; break; case LOAD_POST_SUCCESS: draft.loadPostLoading = false; draft.loadPostDone= true; draft.mainPosts = action.data.concat(draft.mainPosts); draft.hasMorePost = false; break; case LOAD_POST_FAILURE: draft.loadPostLoading = false; draft.loadPostError = action.error; break; case ADD_POST_REQUEST: draft.addPostLoading = true; draft.addPostDone= false; draft.addPostError= null; break; case ADD_POST_SUCCESS: draft.addPostLoading = false; draft.addPostDone= true; draft.mainPosts.unshift(dummyPost(action.data)); break; case ADD_POST_FAILURE: draft.addPostLoading = false; draft.addPostError = action.error; break; case REMOVE_POST_REQUEST: draft.removePostLoading = true; draft.removePostDone = false; draft.removePostError = null; break; case REMOVE_POST_SUCCESS: draft.removePostLoading = false; draft.removePostDone = true; draft.mainPosts = draft.mainPosts.filter((v) => v.id !== action.data); break; case REMOVE_POST_FAILURE: draft.removePostLoading = false; draft.removePostError = action.error; break; case ADD_COMMENT_REQUEST: draft.addCommentLoading = true; draft.addCommentDone = false; draft.addCommentError = null; break; case ADD_COMMENT_SUCCESS:{ const post = draft.mainPosts.find((v) => v.id === action.data.postId); post.Comments.unshift(dummyComment(action.data.content)); draft.addCommentLoading = false; draft.addCommentDone = true; break; // const postIndex = state.mainPosts.findIndex((v) => v.id === action.data.postId); // const post = { ...state.mainPosts[postIndex] }; // post.Comments = [dummyComment(action.data.content), ...post.Comments]; // const mainPosts = [...state.mainPosts]; // mainPosts[postIndex] = post; // return { // ...state, // mainPosts, // addCommentLoading: false, // addCommentDone: true, // }; } case ADD_COMMENT_FAILURE: draft.addCommentLoading = false; draft.addCommentError = action.error; break; default: break; } }); }; export default reducer; sagas/post.js import { delay, fork, all, takeLatest, put, call} from "redux-saga/effects"; import shortId from "shortid"; import axios from 'axios'; // import Axios from 'axios'; // import qs from 'query-string'; import Cookies from 'universal-cookie'; import { ADD_COMMENT_FAILURE, ADD_COMMENT_REQUEST, ADD_COMMENT_SUCCESS, ADD_POST_FAILURE, ADD_POST_REQUEST, ADD_POST_SUCCESS, ADD_POST_TO_ME, generateDummyPost, LOAD_POST_FAILURE, LOAD_POST_REQUEST, LOAD_POST_SUCCESS, REMOVE_POST_FAILURE, REMOVE_POST_REQUEST, REMOVE_POST_SUCCESS } from "../reducers/post"; import { REMOVE_POST_OF_ME } from "../reducers/user"; const cookies = new Cookies(); function addPostAPI(data) { const accessToken = cookies.get("accessToken"); const userEmail = cookies.get("userEmail"); const newObj = { description : data, email : userEmail } return axios.post('/auth/post', newObj,{ headers:{ 'Authorization': `Bearer ${accessToken}`, "Content-Type": "application/json" } }); } function* addPost(action) { try { const result = yield call(addPostAPI, action.data); yield put({ type: ADD_POST_SUCCESS, data: { id : result.data.postId, content: action.data } }) yield put({ type: ADD_POST_TO_ME, data: result.data.postId, }) } catch (error) { console.log(error); yield put({ type: ADD_POST_FAILURE, data: error.data }) } } function loadPostAPI(data) { return axios.get('/api/posts'); } function* loadPost(action) { try { const result = yield call(loadPostAPI); console.log(result); console.log(result.data); yield put({ type: LOAD_POST_SUCCESS, data: result.data.result, }); } catch (error) { console.log(error); yield put({ type: LOAD_POST_FAILURE, data: error.data }) } } function removePostAPI(data) { return axios.post('/api/post', data); } function* removePost(action) { try { delay(1000); // const result = yield call(addPostAPI, action.data); const id = shortId.generate(); yield put({ type: REMOVE_POST_SUCCESS, data: action.data }); yield put({ type: REMOVE_POST_OF_ME, data: action.data }) } catch (error) { yield put({ type: REMOVE_POST_FAILURE, data: error.data }) } } function addCommentAPI(data) { return axios.post('/api/post/${data.postId}/comment', data); } function* addComment(action) { try { delay(1000); // const result = yield call(addPostAPI, action.data); yield put({ type: ADD_COMMENT_SUCCESS, data: action.data }) } catch (error) { yield put({ type: ADD_COMMENT_FAILURE, data: error.data }) } } function* watchAddPost(){ yield takeLatest(ADD_POST_REQUEST, addPost); // 첫번째것만 하고 싶으면 takeLeading } function* watchLoadPost(){ yield takeLatest(LOAD_POST_REQUEST, loadPost); // 첫번째것만 하고 싶으면 takeLeading } function* watchRemovePost(){ yield takeLatest(REMOVE_POST_REQUEST, removePost); // 첫번째것만 하고 싶으면 takeLeading } function* watchAddComment(){ yield takeLatest(ADD_COMMENT_REQUEST, addComment); // 첫번째것만 하고 싶으면 takeLeading } export default function* postSaga(){ yield all([ fork(watchLoadPost), fork(watchAddPost), fork(watchRemovePost), fork(watchAddComment), ]) };
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
jwt 관련 질문
안녕하세요 제로초님 jwt로 구현을 해보려고 시도 중입니다!이해가 안되는 것들이 많아 질문 남깁니다 ㅠㅠ 1. 서버에서 jwt토큰을 발급할때 클라이언트에서 사용할 데이터들(이름, 주소, 프로필 이미지 등)을 jwt 토큰 페이로드에 넣어서 보내고 클라이언트에선 토큰만으로 해당 데이터를 추출하여 사용하는 방식이 맞는건가요? 2. refresh 토큰은 서버에서 별도로 저장하지만 클라이언트에서도 access 토큰이 만료되면 refresh 토큰을 보내야 해서 클라이언트에서도 별도로 저장을 해야할지 싶은데 로컬스토리지나 쿠키에 저장을 한다면 결국 탈취당할 위험이 있기에 클라이언트에서 refresh토큰을 어디에 보관해야하는지 궁금합니다. 3. XSS 공격을 막기위해 cookie에 HttpOnly 옵션으로 자바스크립트에서 접근을 못하게 막는다면 클라이언트 개발하는 코드에서도 document.cookie 로 쿠키에 접근을 못하는게 맞나요??4. 쿠키에 저장하게 된다면 모든 요청마다 헤더에 쿠키정보가 자동으로 담겨서 보내지는 걸로 알고있는데 그렇기에 쿠키보다 로컬스토리지를 사용하는 방법이 더 선호되는지 궁금합니다!
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
n:m 관계 정의 시 궁금점
n:m 관계 정의 시에 궁금한 점이 있어서 질문 남깁니다. 동적인 카테고리가 있는 게시판의 경우 유저가 해당 카테고리에 게시글을 쓰는 걸 구현하기 위해서 category 라는 테이블을 만들고 user가 category에 여러 게시글을 쓸 수 있고 여러 카테고리에 게시글을 쓸 수 있어서 n:m 관계라고 정의했습니다. user - board - category 이런 식으로 되어있을 때 board에서 user의 정보와 category의 정보가 필요할 때 각각을 가지고 올 수 있어야 하는데sequelize에서는 관계가 없기 때문에 가져올 수 없다고 하더군요 이런 경우에는 user-category n:m으로 관계를 정의 하는게 아닌 user-board n:1 board-category 1:n 으로 정의하는 것이 맞을까요??
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
정의 방법
강의 잘 보고 있습니다. : ) Sequelize 공식 문서를 보니까 영상과 동일한 스타일의 모델 정의 방법을 찾을 수가 없어서 공식 문서대로 다음과 같이 작성했는데 문제 없나요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
a태그 noopener, noreferrer 필요한 이유
안녕하세요. 제로초님강의를 듣다, a태그에 noopener, noreferrer에 대해 말씀주셨는데, 해당 속성에 관해서 전에 정리한 글이 있어 공유 드립니다.https://nuhends.tistory.com/107
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
라우트 이동 및 새로고침시 각 컴포넌트별 state 유지
안녕하세요 제로초님 포트폴리오를 nextjs로 새로만들면서 작업중인데 라우트 이동 및 새로고침시에도 각 컴포넌트별 state를 유지하는 효율적인 방법을 좀 알고싶습니다. url : https://dv-node-portfolio.herokuapp.com/git : https://github.com/dvisign/new-next-portfolio전에 리액트로 할때는 useHistory를 이용한 커스텀 훅스 route state에 저장하였는데 nextjs에서는 사용할수가 없어서 useRouer로 저장이 가능한가 확인해보았는데 안되더라구요 그래서 다음으로 알아본것이 redux-persist 라이브러리였는데 리뉴얼된 강좌에서 ssr때문에 HYDRATE 액션때문에 사용은 해보지 않았고 리액트로 redux-persist 라이브러리를 사용했을때 로컬스토리지에 담는것같아 이용하지 않았습니다(개인적으로 localstorage를 신뢰하지 않는 개인적 성향때문에 그렇습니다.) 예를들어서 사이드 네비게이션이 열려있는것을 닫았다가 다른 라우트로 이동했을때 다시 열려있는 현상입니다. 스크린샷을 첨부합니다. 부단 이 부분 뿐만 아니라 나중에 더 만들다보면 이런 state유지 할것들이 더 많아질텐데 어떻게 유지 시키는것이 가장 효율적인 방법일까요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
상품 결제? 관계질문입니다.
안녕하세요 제로초님 유저가 결제한 목록을 db에 저장하려합니다. 결제 목록의 관계는 Cart와 User 간의 다대다 관계로 만들었습니다. ( 카트에 담긴 아이템에서 체크한 아이템들을 보내고 유저아이디와 함께 결제목록을 만들기 위해서) 유저가 결제 버튼을 누를때 Cart에서 체크 한 cart item id 값들을 백엔드로 배열로 보내고 이 값들을 payment table의 CartId에 등록하려합니다. (CartId와 UserId를 제외한 컬럼은 모두 payment table의 기본 값입니다.) payment table의 기본값을 가지고 배열로 받은 CartId의 값만 다르게 데이터를 여러 개를 만들고 싶은데 잘 되지 않습니다 . 아래와 같이 코드를 작성해보았습니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
serverSide Cookie 질문 드립니다. (axios interceptor)
안녕하세요. 강의 잘 듣고있습니다. 다름이 아니라 getServerSideRendering을 할 때 쿠키를 사용하여 사용자 정보를 얻는 로직을 처리하고 있는데 궁금증이 생겨서 질문드립니다. 우선 문제 상황은 axios interceptor를 사용하여 쿠키를 보낼 때 request Header에 쿠키값이 담기지 않는다는 것 입니다. 구글링 등 반나절 한거같은데 정확한 원인을 모르겠어서 질문드립니다.. 1. serverSideProps 쪽 코드 입니다 2. saga file 입니다. 여기서 문제가 발생했던 곳이 interceptor 내부에 있는 innerApi.loadMyInfo를 호출 할 때 request Header에 cookie값이 적용이 안되더라구요 config에 withCredential : true로 적용을 했는데도요.. 여기까지가 문제였고 정상적으로 데이터를 받아오는 소스는 아래와 같습니다 위 이미지와 같이 interceptor를 활용 안하고 바로 불러오면 Header에 Cookie가 담겨서 API 콜이 정상적으로 되더라구요.. 의심이라고 할만한곳은 Host에서 차이가 있는 부분밖에 없는데 (interceptor는 포트포워딩을 한 서버의 IP / port가 나오고 아닌경우는 localhost라고 나오더라구요) 이해가 안가는게 interceptor는 withCredentials 설정까지 해줘도 안되고 그냥 호출한 부분은 아무런 코드작성 없이도 정상적으로 된것입니다... 개인적으로 모든 Api 콜을 interceptor를 활용하여 하고싶은데 혹시 방법이 있을까요? 긴 글 읽어주셔서 감사드립니다..
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
시퀄라이즈 여러 쿼리 보내기, Promise async await 개념
안녕하세요. 항상 강의 보면서 도움 많이 받고 있습니다. 프론트에서 체크박스로 제품을 열람가능한 유저를 선택해서 DB에서 기존 데이터를 삭제하고 체크한 유저를 다시 추가해주는 식으로 제품열람가능 유저를 업데이트 하려고 하는데요 제품모델과 유저 모델은 Item.associate = (db) => { // 릴레이션(관계) 정의 db.Item.belongsTo(db.User); // 제품 등록한 유저 db.Item.belongsToMany(db.User, { through: 'ItemUsers', as: 'ItemViewUsers'}); // 제품 노출할 유저 }; .................. User.associate = (db) => { // 릴레이션(관계) 정의 db.User.belongsToMany(db.User, { through: 'UsersRelation', as: 'Providers', foreignKey: 'customerId' }); // 판매자-구매자 관계 db.User.belongsToMany(db.User, { through: 'UsersRelation', as: 'Customers', foreignKey: 'providerId' }); // 판매자-구매자 관계 db.User.belongsToMany(db.Item, { through: 'ItemUsers', as: 'UserViewItems' }); // 열람가능한 제품 }; 이런식입니다. 프론트는 Form과 Checkbox로 단일 ItemId와 UserId 배열을 보내주고 백에서 req.body로 받는 데이터는 { id: '1', values: { customerIds: [ 'tttt', 'ttt3' ] } } 이런식으로 받습니다. // 제품에 열람가능한 고객 등록 router.post('/add-customer', isLoggedIn, async (req, res, next) => { try { console.log('고객등록 req.body',req.body); console.log(req.user.id) const item = await Item.findOne({ where: { id: req.body.id} }); if (!item) { return res.status(404).send('해당 제품이 존재하지 않습니다.'); } if (item.UserId !== req.user.id) { return res.status(404).send('권한이 없습니다.'); } const itemViewUsers = await item.getItemViewUsers(); //제품 열람가능한 유저 전부 제거 if (itemViewUsers) { await Promise.all( itemViewUsers.map( customer => { console.log('\x1b[36m 유저 제거 시도', customer.id); item.removeItemViewUsers(customer.id); }) ); } // 제품 열람가능한 유저 추가 (체크박스로 체크된) if(req.body.values.customerIds){ await Promise.all( req.body.values.customerIds.map( customerId => { console.log('\x1B[31m 유저 추가 시도', customerId); const user = User.findOne({ where: { id: customerId }}) if (!user){ return res.status(404).send('해당 유저가 존재하지 않습니다.'); } item.addItemViewUsers(customerId); }) ); } res.status(200).json(item); } catch (error) { console.error(error); next(error); // status 500 } }); 유저 tttt가 이미 등록된 상태에서 유저 tttt, ttt3 를 선택해서 등록하면 DB에는 ttt3만 등록된 결과가 나옵니다... 실제 INSERT 문도 하나만 실행되네요. 항상 이런건 아니고 될때도 있습니다. 비동기 부분이 꼬여서 그런것같은데 제가 promise 개념이 약해서 어떤식으로 해결할 수 있을지 궁금합니다. Promise, async, await 부분은 제로초님 강의 꼭 다시 보겠습니다. 백엔드 로그는 이러합니다. 고객등록 req.body { id: '1', values: { customerIds: [ 'tttt', 'ttt3' ] } } tester1 Executing (default): SELECT `id`, `codeName`, `name`, `packageName`, `unit`, `msrp`, `supplyPrice`, `imgSrc`, `createdAt`, `updatedAt`, `UserId` FROM `Items` AS `Item` WHERE `Item`.`id` = '1'; Executing (default): SELECT `User`.`id`, `User`.`password`, `User`.`company`, `User`.`name`, `User`.`phone`, `User`.`email`, `User`.`role`, `User`.`createdAt`, `User`.`updatedAt`, `ItemUsers`.`createdAt` AS `ItemUsers.createdAt`, `ItemUsers`.`updatedAt` AS `ItemUsers.updatedAt`, `ItemUsers`.`ItemId` AS `ItemUsers.ItemId`, `ItemUsers`.`UserId` AS `ItemUsers.UserId` FROM `Users` AS `User` INNER JOIN `ItemUsers` AS `ItemUsers` ON `User`.`id` = `ItemUsers`.`UserId` AND `ItemUsers`.`ItemId` = 1; 유저 제거 시도 tttt 유저 추가 시도 tttt 유저 추가 시도 ttt3 Executing (default): DELETE FROM `ItemUsers` WHERE `ItemId` = 1 AND `UserId` IN ('tttt') Executing (default): SELECT `id`, `password`, `company`, `name`, `phone`, `email`, `role`, `createdAt`, `updatedAt` FROM `Users` AS `User` WHERE `User`.`id` = 'tttt'; Executing (default): SELECT `createdAt`, `updatedAt`, `ItemId`, `UserId` FROM `ItemUsers` AS `ItemUsers` WHERE `ItemUsers`.`ItemId` = 1 AND `ItemUsers`.`UserId` IN ('tttt'); POST /item/add-customer/ 200 9.485 ms - 236 Executing (default): SELECT `id`, `password`, `company`, `name`, `phone`, `email`, `role`, `createdAt`, `updatedAt` FROM `Users` AS `User` WHERE `User`.`id` = 'ttt3'; Executing (default): SELECT `createdAt`, `updatedAt`, `ItemId`, `UserId` FROM `ItemUsers` AS `ItemUsers` WHERE `ItemUsers`.`ItemId` = 1 AND `ItemUsers`.`UserId` IN ('ttt3'); Executing (default): INSERT INTO `ItemUsers` (`createdAt`,`updatedAt`,`ItemId`,`UserId`) VALUES ('2021-12-22 05:56:16','2021-12-22 05:56:16',1,'ttt3'); Executing (default): SELECT `id`, `password`, `company`, `name`, `phone`, `email`, `role`, `createdAt`, `updatedAt` FROM `Users` AS `User` WHERE `User`.`id` = 'tester1'; Executing (default): SELECT `User`.`id`, `User`.`company`, `User`.`name`, `User`.`phone`, `User`.`email`, `User`.`role`, `User`.`createdAt`, `User`.`updatedAt`, `Customers`.`id` AS `Customers.id`, `Customers`.`company` AS `Customers.company`, `Customers`.`name` AS `Customers.name`, `Customers->UsersRelation`.`createdAt` AS `Customers.UsersRelation.createdAt`, `Customers->UsersRelation`.`updatedAt` AS `Customers.UsersRelation.updatedAt`, `Customers->UsersRelation`.`customerId` AS `Customers.UsersRelation.customerId`, `Customers->UsersRelation`.`providerId` AS `Customers.UsersRelation.providerId` FROM `Users` AS `User` LEFT OUTER JOIN ( `UsersRelation` AS `Customers->UsersRelation` INNER JOIN `Users` AS `Customers` ON `Customers`.`id` = `Customers->UsersRelation`.`customerId`) ON `User`.`id` = `Customers->UsersRelation`.`providerId` WHERE `User`.`id` = 'tester1'; Executing (default): SELECT `id`, `password`, `company`, `name`, `phone`, `email`, `role`, `createdAt`, `updatedAt` FROM `Users` AS `User` WHERE `User`.`id` = 'tester1'
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
노드버드를 마치며
현재 노드버드를 마치며 프론트는 리액트 기초 사용법과 노드버드를 만든것이 전부이고 백엔드는 node를 사용하여 노드버드 서버를 구성하고 배포한 것이 전부 입니다. 개인 프로젝트를 시작하기 앞서서 풀스텍으로 할수있을 만큼 최대한 구현을 하려하나 프론트와 백 중에 어떤 포지션에 더 비중을 둬야할지와 개인적으로 어떤 포지션이 맞는지 고민이 많이 됩니다 (공부가 아직 많이 부족하지만..) 프론트는 기술이 빠르게 변화한다는 점이 있다는걸 들었고 사용자와 직접적으로 연결이 되기 때문에 UI/UX는 프론트 개발자의 기본이라고 알고 있습니다, 백엔드는 비지니스 로직에만 집중하며, 어떠한 서비스의 확장성과 안전성에 집중한다고 스치듯 들었던 기억이 납니다. 백엔드는 node로 구성 할 수 있지만, 미래를 생각하면 java를 배워야되는건가 고민이 됩니다. 현재 큰 기업들은 보통 java를 쓰고 있다고 들어서 만약 제가 백엔드를 고려한다면 java를 배워 경력을 쌓아야되는건지 고민이 됩니다. 제로초님의 조언과 의견을 조금 듣고싶습니다
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
글쓴게 안올라가요.. 글등록은됩니다
post form 우선 포스관련에서 코드올립니다 . ㅠㅠ 잘따라하고 글만 안써지는 문제가 생겨서 ㅅ콘솔로그 찍어보니 onChange 는 잘먹히는 상황인데 서서브밋시 포스트 등록은되나 글은 업로드가 되지 않습니다 .. 문제가 무엇인지 모모르겠습니다. import React, { useRef, useCallback, useState, useEffect } from "react"; import { Form, Input, Button } from "antd"; import { useDispatch, useSelector } from "react-redux"; import { addPost } from "../reducer/post"; const PostForm = () => { const { imagePaths, postAdded } = useSelector((state) => state.post); const [text, setText] = useState(""); const dispatch = useDispatch(); const imageInput = useRef(); const onClickImageUpload = useCallback(() => { imageInput.current.click(); }, [imageInput.current]); useEffect(() => { if (postAdded) { setText(""); } }, [postAdded]); const onChangeText = useCallback((e) => { setText(e.target.value); }, []); const onSubmit = useCallback(() => { dispatch(addPost); }, []); return ( <Form style={{ margin: "10px 0 20px" }} encType="multipart/form-data" onFinish={onSubmit} > <Input.TextArea value={text} onChange={onChangeText} maxLength={140} placeholder="어떤 신기한 일이 있었나요?" /> <div> <input type="file" multiple hidden ref={imageInput} /> <Button onClick={onClickImageUpload}>이미지 업로드</Button> <Button type="primary" style={{ float: "right" }} htmlType="submit"> 짹짹 </Button> </div> <div> {imagePaths.map((v) => { return ( <div key={v} style={{ display: "inline-block" }}> <img alt={v} /> <div> <Button>제거</Button> </div> </div> ); })} </div> </Form> ); }; export default PostForm; post reducer export const initialState = { mainPosts: [ { id: 1, User: { id: 1, nickname: "제로초", }, content: "첫 번째 게시글", Images: [ { src: "https://bookthumb-phinf.pstatic.net/cover/137/995/13799585.jpg?udate=20180726", }, { src: "https://gimg.gilbut.co.kr/book/BN001958/rn_view_BN001958.jpg", }, { src: "https://gimg.gilbut.co.kr/book/BN001998/rn_view_BN001998.jpg", }, ], Comments: [ { User: { nickname: "nero", }, content: "우와 개정판이 나왔군요~", }, { User: { nickname: "hero", }, content: "얼른 사고싶어요~", }, ], }, ], imagePaths: [], postAdded: false, }; const ADD_POST = "ADD_POST"; export const addPost = { type: ADD_POST, }; const dummyPost = { id: 2, content: "더미데이터입니다.", User: { id: 1, nickname: "제로초", }, Images: [], Comments: [], }; const reducer = (state = initialState, action) => { switch (action.type) { case ADD_POST: { return { ...state, mainPosts: [dummyPost, ...state.mainPosts], postAdded: true, }; } default: { return { ...state, }; } } }; export default reducer; postcard import { Card, Button, Avatar, Popover, List, Comment } from "antd"; import { Content } from "antd/lib/layout/layout"; import PropTypes from "prop-types"; import React from "react"; import { RetweetOutlined, HeartTwoTone, HeartOutlined, MessageOutlined, EllipsisOutlined, } from "@ant-design/icons"; import { useSelector } from "react-redux"; import PostImages from "./PostImages"; const PostCard = ({ post }) => { const { me } = useSelector((state) => state.user); const id = me?.id; return ( <div> <Card cover={post.Images[0] && <PostImages images={post.Images} />} actions={[ <RetweetOutlined key="retweet" />, <HeartOutlined key="heart" />, <MessageOutlined key="message" />, <Popover key="more" content={ <Button.Group> {id && post.User.id === id ? ( <> <Button>수정</Button> <Button type="danger">삭제</Button> </> ) : ( <Button>신고</Button> )} </Button.Group> } > <EllipsisOutlined /> </Popover>, ]} > <Card.Meta avatar={<Avatar>{post.User.nickname[0]}</Avatar>} title={post.User.nickname} /> {/* <Image /> */} <Content /> </Card> {/* <CommentForm /> */} {/* <Comments /> */} </div> ); }; PostCard.propTypes = { post: PropTypes.shape({ id: PropTypes.number, User: PropTypes.object, content: PropTypes.string, createdAt: PropTypes.object, Comments: PropTypes.arrayOf(PropTypes.any), Images: PropTypes.arrayOf(PropTypes.any), }), }; export default PostCard;
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
next에서 리액트사용시 자동완성 질문있습니다!
import React, { useState} from 'react'; 이부분에서 useState를 추가할떄에는 자동완성을 지원하는데 const LogimForm = () => { const [id, setId] = useState(''); // 이부분에서 useState를 타이핑할경우 자동완성과 import를 자동추가하지않는데 vsc에서의 해결법이있는지 궁금합니다 const [password, setPassword] = useRef(null); return ( <form> ... </form> ); }; export default LogimForm; 비슷한 질문글이 있어 확인을 해보았는데요 답변으로 웹스톰 고유기능이라고 하신답글을 보긴하였는데 vsc에서도 자동완성이되었는데 next내부라는 이유때문에 자동완성이 되지않는지 해결법은 있는지 궁금합니다 감사합니다!
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
babel-eslint 대신..
babel-eslint가 deprecated되고 @babel/eslint-parser로 사용되는 것 같아서 세팅을 해보았습니다만, airbnb가 잡아주지 못하고 있는 거 같은데 어떤 부분이 잘못된 건지 한번 봐주실 수 있나요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
es린트 노드버드 폴더만 적용이안되는 문제
기존에 공부했던 개인프로젝트에서는 es린트가 정상적으로 오류가 발생했었는데 오류가 발생코드를 노드버드 프로젝트에 넣거나 린트문서에서 제공하는 오류유발코드를 넣어봐도 es린트 오류가 발생하지않습니다 검색후 프리티어 문제일까도 싶었지만 해결이되지않았습니다 ㅜㅜ 자문구해봅니다 package.json { "name": "react-nodebird-front", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "next", "build": "next build" }, "author": "Kangikchi", "license": "ISC", "dependencies": { "next": "^9.5.5" }, "devDependencies": { "eslint": "^8.5.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.3", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.27.1", "eslint-plugin-react-hooks": "^4.3.0", "prettier": "^2.5.1" } } es린트파일 { "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 2020, "sourceType": "module", "ecmaFeatures": { "jsx": true } }, "env": { "browser": true, "node": true, "es6": true }, "extends": ["eslint:recommended", "plugin:react/recommended"], "plugins": ["prettier", "import", "react-hooks"], "rules": { "indent": ["error", 2], // 오류유발을 위해 공백칸룰을 추가해보았지만 eslint오류알림이 발생하지않았습니다 "prettier/prettier": "error" } }
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
새로 올리신 리액트쿼리 버전에서 getServerSideProps대신 getStaticProps 쓰는 이유가 궁금합니다.
안녕하세요. 자꾸 귀찮게 해드려서 죄송합니다. 리액트쿼리로 바꾸신거 조금씩 따라하면서 변형하고 있는데 /user/[id] /post/[id] 에서 getServerSideProps대신 getStaticProps 로 바뀐걸 봤습니다. 정적은 빌드시에 데이터 불러오는거라서 데이터 변경가능성이 있는 post/[id]같은 경우는 정적을 쓰면 안될것같은 느낌인데 무슨 장점이나 혹시 바꾸신 이유가 있을까요? 그리고 queryClient.prefetchQuery에 대해서도 궁금한점이 있습니다. 현재 상품을 등록하는 /item/register.tsx를 작성중이고 로그인된 유저에서 정보를 가져와야 해서 서버사이드 랜더링을 적용중에 있습니다. 유저정보를 가져오는 api export function loadMyInfoAPI() { return axios.get('/user').then((response) => response.data); } back에서 라우터 부분 // 로그인 유저 정보 얻기 router.get('/', async (req, res, next) => { // GET /user 로그인 유지 위해 로그인한 유저의 정보 전송 console.log('req.user?', req.user); try { if (req.user) { // 로그인 됐을경우 const userDataWithoutPassword = await User.findOne({ // 프론트로 보낼 유저 정보를 재가공 where: { id: req.user.id }, attributes: { exclude: ['password'], // 비밀번호 제외 }, include: [ { model: User, as: "Customers", attributes: ["id", "company"], exclude: ['UsersRelation'], } ] }) res.status(200).json(userDataWithoutPassword); console.log(JSON.stringify(userDataWithoutPassword)); } else { res.status(200).json(null); } } catch (error) { console.error(error); next(error); } }); 프론트 /item/register.tsx에서 기본 골자는 const Register = () => { ... const { data: myUserInfo } = useQuery<User>('user', loadMyInfoAPI); ... 중략 return ( <AppLayout> <div>{JSON.stringify(myUserInfo)}</div><br /> ... 중략 <div> <List className="demo-loadmore-list" itemLayout="horizontal" dataSource={myUserInfo.Customers} renderItem={(item) => ( <List.Item key={item.id} actions={[ <> <Button type="primary" shape="circle">등록</Button> </> ]} > <List.Item.Meta title={ <> {item.company}<span> | </span><span>담당자: {item.id}</span> </>} /> </List.Item> )} /> </div> 이렇게 로그인된 유저 정보를 출력해주는 부분이 있습니다. myUserInfo.Customers는 me.followers와 흡사한 개념으로 맍들었고요. 서버사이드 랜더링은 export const getServerSideProps = async (context: GetServerSidePropsContext) => { const cookie = context.req ? context.req.headers.cookie : ''; axios.defaults.headers.Cookie = ''; if (context.req && cookie) { axios.defaults.headers.Cookie = cookie; } const response = await loadMyInfoAPI(); if (!response) { return { redirect: { destination: '/', permanent: false, }, }; } return { props: {}, }; }; 단순히 로그인체크만 해서 로그인이 안됐을경우 리다이랙트만 해주는 서버사이드랜더링을 하면 back에서 console.log('req.user?', req.user); 값은 정상적으로 user객체를 출력합니다. 프론트 페이지도 정상적으로 데이터가 나오고요. JSON.stringify(myUserInfo)로 출력한 데이터는 object {9} id : tester1 company : testers name : test phone : 101010 email : 2 role : NOVICE createdAt : 2021-12-17T04:43:25.000Z updatedAt : 2021-12-17T04:43:25.000Z Customers [2] 0 {2} id : ttt3 UsersRelation {4} 1 {2} id : tttt UsersRelation {4} createdAt : 2021-12-17T08:05:07.000Z updatedAt : 2021-12-17T08:05:07.000Z customerId : tttt providerId : tester1 이렇게 나옵니다. 물론 데이터를 받아와서 주입해주는 부분이 없기때문에 /item/register로 주소를 쳐서 들어가면 오류가 납니다. 데이터를 주입시켜주기 위해 쿼리클라이언트를 사용할 경우 export const getStaticProps = async () => { const queryClient = new QueryClient(); await queryClient.prefetchQuery(['user'], () => loadMyInfoAPI()); return { props: { dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))), }, }; }; back에서 user는 undefined로 나오게 되고 오류가 발생합니다.. 어떠한 부분때문에 같은loadMyInfoAPI 를 쓰는데도 불구하고 다른 결과가 나오는지 궁금함니다. 프론트 터미널의 오류메시지 TypeError: Cannot read property 'Customers' of undefined at Register (D:\3programming\excuse_moa\front_rq\.next\server\pages\item\register.js:2657:36) at processChild (D:\3programming\excuse_moa\front_rq\node_modules\react-dom\cjs\react-dom-server.node.development.js:3353:14) at resolve (D:\3programming\excuse_moa\front_rq\node_modules\react-dom\cjs\react-dom-server.node.development.js:3270:5) at ReactDOMServerRenderer.render (D:\3programming\excuse_moa\front_rq\node_modules\react-dom\cjs\react-dom-server.node.development.js:3753:22) at ReactDOMServerRenderer.read (D:\3programming\excuse_moa\front_rq\node_modules\react-dom\cjs\react-dom-server.node.development.js:3690:29) at Object.renderToString (D:\3programming\excuse_moa\front_rq\node_modules\react-dom\cjs\react-dom-server.node.development.js:4298:27) at renderPage (D:\3programming\excuse_moa\front_rq\node_modules\next\dist\server\render.js:596:45) at Object.ctx.renderPage (D:\3programming\excuse_moa\front_rq\.next\server\pages\_document.js:1161:30) at Function.getInitialProps (D:\3programming\excuse_moa\front_rq\.next\server\pages\_document.js:621:19) at Function.getInitialProps (D:\3programming\excuse_moa\front_rq\.next\server\pages\_document.js:1169:87) error - Error: "MyDocument.getInitialProps()" should resolve to an object. But found "undefined" instead. at Object.loadGetInitialProps (D:\3programming\excuse_moa\front_rq\node_modules\next\dist\shared\lib\utils.js:75:15) at runMicrotasks (<anonymous>) at processTicksAndRejections (internal/process/task_queues.js:93:5) at async renderDocument (D:\3programming\excuse_moa\front_rq\node_modules\next\dist\server\render.js:609:30) at async Object.renderToHTML (D:\3programming\excuse_moa\front_rq\node_modules\next\dist\server\render.js:647:28) at async doRender (D:\3programming\excuse_moa\front_rq\node_modules\next\dist\server\next-server.js:1149:38) at async D:\3programming\excuse_moa\front_rq\node_modules\next\dist\server\next-server.js:1241:28 at async D:\3programming\excuse_moa\front_rq\node_modules\next\dist\server\response-cache.js:64:36 { page: '/item/register' } back의 터미널 로그 req.user? undefined GET /user 200 1.716 ms - 4