Resolved
Written on
·
48
0
강사님 안녕하세요, 백엔드 서버 연결하고 게시물(PostForm.tsx) 올리는 부분에서 오류가 발생하여 질문드립니다. (섹션5 - 게시글 업로드 완성)
hydration 이슈를 찾아봤는데 (https://velog.io/@jhplus13/NextJS-React-Hydration-Error-%ED%95%B4%EA%B2%B0%EA%B8%B0) typeof window과 관련있는거 같은데 혼자 해결하기 어려워 링크 첨부드립니다..!
오류해결을 위해 (hydration / PostRecommends.tsx) 해당 부분을 수정하였습니다..!
변수입력확인(Date.now(),
Math.random(),
new Date()
) + faker 해당부분은 주석처리 했습니다.
태그 중첩 확인, layout.tsx font 연결 확인
[콘솔 에러]
intercept-console-error.js:56 A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:
- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.
https://react.dev/link/hydration-mismatch
...
<HotReload assetPrefix="">
<ReactDevOverlay state={{nextId:1, ...}} dispatcher={{...}}>
<DevRootNotFoundBoundary>
<NotFoundBoundary notFound={<NotAllowedRootNotFoundError>}>
<NotFoundErrorBoundary pathname="/home" notFound={<NotAllowedRootNotFoundError>} notFoundStyles={undefined} ...>
<RedirectBoundary>
<RedirectErrorBoundary router={{...}}>
<Head>
<link>
<RootLayout>
<html lang="ko" className="__variable...">
<body
className="__className_fde3a9"
- cz-shortcut-listen="true"
>
...
...
[Fragment key값 오류내용]
// package.json
{
"name": "zcom",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@auth/core": "^0.27.0",
"@fortawesome/fontawesome-svg-core": "^6.7.1",
"@fortawesome/free-solid-svg-icons": "^6.7.1",
"@fortawesome/react-fontawesome": "^0.2.2",
"@tanstack/react-query": "^5.62.11",
"@vanilla-extract/css": "^1.16.1",
"@vanilla-extract/recipes": "^0.5.5",
"@vanilla-extract/sprinkles": "^1.6.3",
"classnames": "^2.5.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
"next": "^15.0.4",
"next-auth": "^5.0.0-beta.25",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-intersection-observer": "^9.14.1",
"react-textarea-autosize": "^8.5.6",
"zustand": "^5.0.2"
},
"devDependencies": {
"@faker-js/faker": "^9.3.0",
"@tanstack/react-query-devtools": "^5.62.11",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@vanilla-extract/next-plugin": "^2.4.7",
"@vanilla-extract/webpack-plugin": "^2.3.15",
"msw": "^2.6.8",
"typescript": "^5"
},
"msw": {
"workerDirectory": [
"public"
]
}
}
Answer 3
0
세팅 문제 같아 백엔드 서버 세팅 다시 하고 실행해 봤을때 (nest-prisma파일 1월 6일 기준) postForm부분에서 /api/posts에서 500 에러가 발생하고 있습니다..! user 정보(authjs.session-token)도 잘 받아오고 있습니다..ㅠㅜ
// 백엔드 터미널 오류 내용
canActivate with req.user undefined
eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwia2lkIjoieFQ0MjR1azFqQi1xRlFINjRfaVY0OE4tRHRNbzh1b3JHSHZENnhVRFI1Tm5Za3JYRHBGbU5yZlVfd3Z4TmUzT2lqUUdVWFhTYkhGZXYtM295YVM0QUEifQ..W_hVRdWsTS-VthkfaJClwA.FO9GzpLUvS7u13TGyAU0NOm3T31wpThqCsKw6DGWTNiLrB9qPojT46wzKSprfxdphVwaTtuU_T_PWPfKOeQ4CerC1UhmqnCB2NpDdvaqy8dzQhx04DluwW3gtZwsMnsG5s3Y0XV76FBOZ0NyeNFApg0wktotHD-C7bawlWTVYySjr4DI0QfAq0zcLUTJYvJt8eRoicHkYynka9RJaJnqnKESDDfbRt_zHeykblhbRaE.DBUwZAHepn3XCyZgJioJQZCIUQ8zs5GeO5Y0wnY2jkM
[Nest] 33332 - 2025. 01. 06. 오후 4:49:28 ERROR [ExceptionsHandler] no matching decryption secret
Error: no matching decryption secret
at jwtDecrypt.clockTolerance (file:///C:/Users/alsdu/Desktop/nest-prisma-main/node_modules/@auth/core/jwt.js:76:15)
at flattenedDecrypt (file:///C:/Users/alsdu/Desktop/nest-prisma-main/node_modules/jose/dist/node/esm/jwe/flattened/decrypt.js:92:15)
at compactDecrypt (file:///C:/Users/alsdu/Desktop/nest-prisma-main/node_modules/jose/dist/node/esm/jwe/compact/decrypt.js:15:23)
at jwtDecrypt (file:///C:/Users/alsdu/Desktop/nest-prisma-main/node_modules/jose/dist/node/esm/jwt/decrypt.js:5:23)
at decode (file:///C:/Users/alsdu/Desktop/nest-prisma-main/node_modules/@auth/core/jwt.js:67:25)
at LoggedInGuard.canActivate (C:\Users\alsdu\Desktop\nest-prisma-main\src\auth\logged-in-guard.ts:17:23)
at GuardsConsumer.tryActivate (C:\Users\alsdu\Desktop\nest-prisma-main\node_modules\@nestjs\core\guards\guards-consumer.js:16:17)
at canActivateFn (C:\Users\alsdu\Desktop\nest-prisma-main\node_modules\@nestjs\core\router\router-execution-context.js:134:33)
at C:\Users\alsdu\Desktop\nest-prisma-main\node_modules\@nestjs\core\router\router-execution-context.js:42:31
at C:\Users\alsdu\Desktop\nest-prisma-main\node_modules\@nestjs\core\router\router-proxy.js:9:17
[Nest] 33332 - 2025. 01. 06. 오후 4:49:28 LOG [HTTP] POST /api/posts 500 52 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 ::1
// .env
NEXT_PUBLIC_BASE_URL=http://localhost:9090
AUTH_SECRET=비번
NEXT_PUBLIC_MODE=local
NEXT_PUBLIC_MSW_ENABLED=false
// postForm.tsx(onSubmit)
const onSubmit: FormEventHandler = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('content', content);
preview.forEach((p) => {
p && formData.append('images', p.file);
})
try{
const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/posts`, {
method: 'post',
credentials: 'include',
body: formData,
});
if (response.status === 201) {
setContent('');
setPreview([]);
const newPost = await response.json();
queryClient.setQueryData(['posts', 'recommends'], (prevData: { pages: Post[][] }) => {
const shallow = {
...prevData,
pages: [...prevData.pages],
};
shallow.pages[0] = [...shallow.pages[0]];
shallow.pages[0].unshift(newPost);
return shallow;
});
queryClient.setQueryData(['posts', 'recommends'], (prevData: { pages: Post[][] }) => {
const shallow = {
...prevData,
pages: [...prevData.pages],
};
shallow.pages[0] = [...shallow.pages[0]];
shallow.pages[0].unshift(newPost);
return shallow;
});
queryClient.setQueryData(['posts', 'followings'], (prevData: { pages: Post[][] }) => {
const shallow = {
...prevData,
pages: [...prevData.pages],
};
shallow.pages[0] = [...shallow.pages[0]];
shallow.pages[0].unshift(newPost);
return shallow;
});
}
} catch(err){
alert('업로드 중 에러발생');
}
};
0
0
안녕하세요, 인프런 AI 인턴입니다.
인생꿀잼님께서 제기하신 하이드레이션 오류와 관련하여, 이는 주로 서버와 클라이언트 사이에 렌더링된 HTML이 일치하지 않을 때 발생하는 문제입니다. 이 문제가 발생하는 주된 원인에는 Date.now()
, Math.random()
, new Date()
등의 동적 데이터 사용과 클라이언트 사이드에서만 접근 가능한 window
객체의 사용이 포함됩니다.
동적 데이터 고정: Date.now()
, Math.random()
등의 함수를 서버와 클라이언트에서 동일한 값을 생성하도록 고정해야 합니다. 예를 들어, faker
를 사용할 때는 faker.seed(123)
과 같이 시드를 고정하면 랜덤 생성이 일관되게 됩니다.
키 프로퍼티 설정: React에서 리스트를 렌더링할 때는 각 자식 요소에 고유한 key
를 부여해야 합니다. 이때 key로 인덱스를 사용하는 것은 피하고, 가능한 unique한 식별자를 사용하는 것이 좋습니다. 아래의 작성하신 코드를 기반으로:
data?.pages.map((page) => (
<Fragment key={page.someUniqueId}> // 여기서 someUniqueId는 page 별 고유ID로 변경
{page.posts.map((post) => (
<Post key={post.postId} post={post} />
))}
</Fragment>
));
page
객체에서 고유한 식별자를 찾아 Fragment
의 key
로 설정하세요.
typeof window
사용 검토: 서버 사이드 렌더링(SSR)에서는 클라이언트 전용 객체인 window
에 접근할 수 없습니다. if (typeof window !== 'undefined')
와 같은 조건문으로 클라이언트에서만 실행될 코드를 분리하세요.
더불어, 이 링크에서 제안된 방법은 @faker-js/faker 사용 시 하이드레이션 에러를 해결하는데 참고할 수 있을 것입니다. faker.seed(123)
로 랜덤 데이터를 고정하여 서버와 클라이언트의 불일치를 방지한 사례입니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
백엔드.env에도 AUTH_SECRET 넣어주세요!