해결된 질문
23.08.10 17:43 작성
·
306
0
https://github.com/ZeroCho/react-nodebird/blob/master/toolkit/front/pages/index.js
여기 코드를 가져와서 ssr을 설정했습니다
front 코드 에러로
Error: Hydration failed because the initial UI does not match what was rendered on the server.
Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
이렇게 두개가 나오는데 이걸 어떻게 해결할지 잘 모르겠습니다 initaial UI 에러라길레
initialState: {
user: {
...userInitialState,
me: myInfo,
},
post: {
...postInitialState,
mainPosts: posts,
hasMorePosts: posts.length === 10,
},
},
주석 처리 되어있는 이부분을 어떻게 해야되는거 같은데 잘 모르겠습니다
답변 2
0
2023. 08. 14. 14:57
reducers/index.js
import { combineReducers } from "redux";
import { HYDRATE } from "next-redux-wrapper";
import axios from "axios";
import userSlice from "./user";
import postSlice from "./post";
axios.defaults.baseURL = "http://localhost:3065";
axios.defaults.withCredentials = true;
// (이전상태, 액션) => 다음상태
const rootReducer = (state, action) => {
switch (action.type) {
case HYDRATE:
console.log("HYDRATE", action);
return action.payload;
default: {
const combinedReducer = combineReducers({
user: userSlice.reducer,
post: postSlice.reducer,
});
return combinedReducer(state, action);
}
}
};
export default rootReducer;
오류 해결했습니다 index.js에서 하이드레이트 하는 부분의 문법이 조금 달랐네요!
이 링크가 도움이 됐습니다!!
0
2023. 08. 10. 18:09
이 에러는 어떠한 이유에서든지 서버에서 렌더링한 것과 브라우저에서 렌더링한 것이 일치하지 않아서 발생하는 문제입니다. 사실 무시하셔도 되는 에러이기는 합니다. 해결하려고 할 때는 내 정보가 들어있는 경우 해결하기 쉽지가 않습니다. 애초에 내 정보는 ssr하지 않는 것이 좋습니다. 내 정보는 브라우저에서 따로 불러오세요.
2023. 08. 10. 23:00
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import axios from "axios";
import PostForm from "../components/PostForm";
import PostCard from "../components/PostCard";
import AppLayout from "../components/AppLayout";
import wrapper from "../store/configureStore";
import { loadPosts } from "../reducers/post";
import { loadMyInfo } from "../reducers/user";
const Home = (props) => {
console.log("props", props);
const { me } = useSelector((state) => state.user);
const { mainPosts, hasMorePost, loadPostsLoading, retweetError } =
useSelector((state) => state.post);
const dispatch = useDispatch();
useEffect(() => {
dispatch(loadMyInfo());
}, []);
useEffect(() => {
if (retweetError) {
alert(retweetError);
}
}, [retweetError]);
const lastId = mainPosts[mainPosts.length - 1]?.id;
useEffect(() => {
function onScroll() {
console.log(
window.scrollY,
document.documentElement.clientHeight,
document.documentElement.scrollHeight
);
if (
window.scrollY + document.documentElement.clientHeight >
document.documentElement.scrollHeight - 300
)
if (hasMorePost && !loadPostsLoading) {
dispatch(loadPosts(lastId));
}
}
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
};
}, [hasMorePost, mainPosts]);
return (
<AppLayout>
{me && <PostForm />}
{mainPosts.map((post) => {
return <PostCard key={post.id} post={post} />;
})}
</AppLayout>
);
};
// SSR (프론트 서버에서 실행)
export const getServerSideProps = wrapper.getServerSideProps(
(store) =>
async ({ req }) => {
const cookie = req ? req.headers.cookie : "";
axios.defaults.headers.Cookie = "";
// 쿠키가 브라우저에 있는경우만 넣어서 실행
// (주의, 아래 조건이 없다면 다른 사람으로 로그인 될 수도 있음)
if (req && cookie) {
axios.defaults.headers.Cookie = cookie;
}
await store.dispatch(loadPosts());
return {
props: {
// initialState: {
// user: {
// ...userInitialState,
// me: myInfo,
// },
// post: {
// ...postInitialState,
// mainPosts: posts,
// hasMorePosts: posts.length === 10,
// },
// },
},
};
}
);
export function reportWebVitals(metric) {
console.log(metric);
}
export default Home;
이런식으로 ssr외부로 loadMyinfo를 밖으로 빼라는 말씀이신가요?
2023. 08. 11. 16:00
이런식으로 loadMyInfo를 밖으로 빼도 똑같은 에러가 발생합니다 그리고 loadMyInfo를 ssr하지 말라는 말씀은 새로고침해도 로그인이 풀렸다 다시 로그인이 되는 현상으로 돌아가라는 말씀이신가요?
2023. 08. 11. 16:04
네, 그냥 유저 정보를 불러올 때 로딩창처럼 띄워서 가리고 나서 유저 정보를 받아오는 것이 낫습니다. 검색 엔진에 나와야하는 데이터가 아니라면 굳이 ssr할 필요가 없습니다.
말씀드렸던것처럼 이 에러는 해결하기가 쉽지 않습니다. 어느 부분에서 서버와 브라우저가 렌더링한 게 달라지는 건지부터 찾아야 합니다.
https://github.com/vercel/next.js/discussions/35773#discussioncomment-2840696
여기에서처럼 수많은 원인들이 있어서 하나씩 다 찾아보아야 합니다. 제일 좋은 건 저 에러가 발생하지 않았던 상황으로 돌아가서 다시 하나씩 추가해보면서 언제부터 발생하는지 찾는 겁니다.
2023. 08. 11. 17:16
그렇군요 그럼 궁금한게 서버사이드 랜더링은 요청이 들어오면 서버에서 페이지를 만들어서 프론트로 보내주는것이라고 이해하고 있는데 서버에서 랜더링한것과 브라우저에서 랜더링 한것이 다르다는 것이 어떻게 발생할 수 있나요?
2023. 08. 11. 17:20
브라우저에서 ssr된 html을 받으면서 리액트 트리를 구성합니다. 왜냐면 앞으로는 브라우저 react가 서버에서 온 데이터를 넘겨받아서 처리하기 때문입니다. 그게 hydration이라는 과정이고요. 이 때 서버에서 온 데이터와 브라우저에서 구성한 트리가 다르게 되면 지금같은 문제가 발생합니다. 예를 들어 Math.random()같은 걸 쓰면 서버랑 브라우저랑 달라지게 됩니다.
그래서 생각난 건데 혹시 지금도 faker 쓰고 계신가요? 이게 랜덤 데이터이긴 합니다.
2023. 08. 11. 18:48
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import axios from 'axios';
import AppLayout from '../components/AppLayout';
import PostForm from '../components/PostForm';
import PostCard from '../components/PostCard';
import { loadMyInfo } from '../reducers/user';
import { loadPosts } from '../reducers/post';
import wrapper from '../store/configureStore';
const Home = (props) => {
console.log('props', props);
const dispatch = useDispatch();
const { me } = useSelector((state) => state.user);
const { mainPosts, hasMorePosts, loadPostsLoading, retweetError } = useSelector((state) => state.post);
useEffect(() => {
if (retweetError) {
alert(retweetError);
}
}, [retweetError]);
const lastId = mainPosts[mainPosts.length - 1]?.id;
useEffect(() => {
function onScroll() {
if (window.pageYOffset + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300) {
if (hasMorePosts && !loadPostsLoading) {
dispatch(loadPosts(lastId));
}
}
}
window.addEventListener('scroll', onScroll);
return () => {
window.removeEventListener('scroll', onScroll);
};
}, [hasMorePosts, loadPostsLoading, mainPosts]);
return (
<AppLayout>
{me && <PostForm />}
{mainPosts.map((post) => <PostCard key={post.id} post={post} />)}
</AppLayout>
);
};
// SSR (프론트 서버에서 실행)
export const getServerSideProps = wrapper.getServerSideProps((store) => async ({ req }) => {
const cookie = req ? req.headers.cookie : '';
axios.defaults.headers.Cookie = '';
// 쿠키가 브라우저에 있는경우만 넣어서 실행
// (주의, 아래 조건이 없다면 다른 사람으로 로그인 될 수도 있음)
if (req && cookie) {
axios.defaults.headers.Cookie = cookie;
}
await store.dispatch(loadPosts());
await store.dispatch(loadMyInfo());
return {
props: {
// initialState: {
// user: {
// ...userInitialState,
// me: myInfo,
// },
// post: {
// ...postInitialState,
// mainPosts: posts,
// hasMorePosts: posts.length === 10,
// },
// },
},
};
});
export function reportWebVitals(metric) {
console.log(metric);
}
export default Home;
위 코드에서 props에 주석처리 되어있는 부분은 왜 주석처리 해놓은건지 알 수 있을까요?
제가 chat gpt로도 검색을 좀 해봤는데 더미데이터로 데이터 형식을 넘겨줘야한다는 식으로 답변이 와서 혹시 저부분이 문제인가 싶어서 그렇습니다
2023. 08. 11. 19:05
오류가 일어나는 위치는
await store.dispatch(loadPosts());
await store.dispatch(loadMyInfo());
이 부분입니다 두개 모두에서 일어납니다 하나씩 지워봤는데 두개 모두에게서 일어났습니다
2023. 08. 11. 19:12
front/reducers/posts.js
import {
createSlice,
createAsyncThunk,
startListening,
createListenerMiddleware,
} from "@reduxjs/toolkit";
import { throttle } from "lodash";
import axios from "axios";
import { HYDRATE } from "next-redux-wrapper";
export const initialState = {
mainPosts: [],
imagePaths: [],
hasMorePost: true,
addPostLoading: false,
addPostDone: false,
addPostError: null,
addCommentLoading: false,
addCommentDone: false,
addCommentError: null,
addRemoveLoading: false,
addRemoveDone: false,
addRemoveError: null,
loadPostsLoading: false,
loadPostsDone: false,
loadPostsError: null,
likePostLoading: false,
likePostDone: false,
likePostError: null,
unlikePostLoading: false,
unlikePostDone: false,
unlikePostError: null,
removePostLoading: false,
removePostDone: false,
removePostError: null,
uploadImagesLoading: false,
uploadImagesDone: false,
uploadImagesError: null,
retweetLoading: false,
retweetDone: false,
retweetError: null,
};
export const loadPosts = createAsyncThunk("/loadposts", async (lastId) => {
throttledFetchData();
const response = await axios.get(`/posts?lastId=${lastId || 0}`);
return response.data;
});
const postSlice = createSlice({
name: "post",
initialState,
reducers: {
// 비동기 액션이기 때문에 async를 설정안해도 된다
removeImage(state, action) {
state.imagePaths = state.imagePaths.filter(
(v, i) => i !== action.payload
);
},
},
extraReducers: (builder) =>
builder
.addCase([HYDRATE], (state, action) => ({
...state,
...action.payload.post,
}))
// loadPosts
.addCase(loadPosts.pending, (state, action) => {
console.log(action);
state.loadPostsLoading = true;
state.loadPostsDone = false;
})
.addCase(loadPosts.fulfilled, (state, action) => {
console.log(action);
state.mainPosts = state.mainPosts.concat(action.payload);
state.hasMorePost = action.payload.length === 10;
state.loadPostsLoading = false;
state.loadPostsDone = true;
})
.addCase(loadPosts.rejected, (state, action) => {
console.log(action);
state.loadPostsLoading = false;
state.loadPostsError = action.error;
})
.addDefaultCase((state) => state),
});
export default postSlice;
back/routes/posts.js
const express = require("express");
const { Op } = require("sequelize");
const { Post, Image, User, Comment } = require("../models");
const router = express.Router();
router.get("/", async (req, res, next) => {
// GET /posts
try {
const where = {};
if (parseInt(req.query.lastId, 10)) {
// 초기 로딩이 아닐 때
where.id = { [Op.lt]: parseInt(req.query.lastId, 10) };
} // 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
const posts = await Post.findAll({
where,
limit: 10,
order: [
["createdAt", "DESC"],
[Comment, "createdAt", "DESC"],
],
include: [
{
model: User,
attributes: ["id", "nickname"],
},
{
model: Image,
},
{
model: Comment,
include: [
{
model: User,
attributes: ["id", "nickname"],
},
],
},
{
model: User, // 좋아요 누른 사람
as: "Likers",
attributes: ["id"],
},
{
model: Post,
as: "Retweet",
include: [
{
model: User,
attributes: ["id", "nickname"],
},
{
model: Image,
},
],
},
],
});
res.status(200).json(posts);
} catch (error) {
console.error(error);
next(error);
}
});
module.exports = router;
dispatch(loadposts())와 관련된 코드들입니다
2023. 08. 10. 22:11
내 정보가 들어있는 경우라는게 정확하게 어떤 경우인지 잘이해가 되질 않습니다. 내 정보라는게 정확이 무었을 말씀하시는 건가요?
또 내 정보는 ssr하지 않는 것이 좋다고 하셨는데 구체적인 방법이 어떻게 되나요?