묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결Next + React Query로 SNS 서비스 만들기
안녕하세요 Next-auth 질문을 올려봅니다.
next-auth로 로그인하기의 강의를 듣던 도중 오류가 생겨 게시판에 글을 남겨봅니다. 문제는 Login 모달에서 로그인을 하였을 때 http://localhost:3000/api/auth/error 로 페이지가 이동 되어집니다.next-auth "next-auth": "^5.0.0-beta.3",.env .env.local 에 AUTH_URL=http://localhost:9090 를 넣어 두었고route.tsexport { GET, POST } from "@/auth";handlers.tsimport { http, HttpResponse, StrictResponse } from "msw"; import { faker } from "@faker-js/faker"; const User = [ { id: "zeroCho", nickName: "zero", image: "/yRsRRjGO.jpg" }, ]; export const handlers = [ // 로그인 http.post("/api/login", () => { console.log("로그인"); return HttpResponse.json(User[1], { headers: { "Set-Cookie": "connect.sid=msw-cookie;HttpOnly;Path=/", // Http cookie 넣어주기 }, }); }), // 로그아웃 http.post("/api/logout", () => { console.log("로그아웃"); return new HttpResponse(null, { headers: { "Set-Cookie": "connect.sid=;HttpOnly;Path=/;Max-Age=0", }, }); }), // 회원 가입 http.post("/api/users", async ({ request }) => { console.log("회원가입"); // return HttpResponse.text(JSON.stringify('user_exists'), { // status: 403, // }) return HttpResponse.text(JSON.stringify("ok"), { headers: { "Set-Cookie": "connect.sid=msw-cookie;HttpOnly;Path=/;Max-Age=0", }, }); }), ];auth.tsimport NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; export const { handlers: { GET, POST }, auth, signIn, } = NextAuth({ pages: { signIn: "/i/flow/login", newUser: "/i/flow/signup", }, providers: [ CredentialsProvider({ async authorize(credentials) { const authResponse = await fetch(`${process.env.AUTH_URL}/api/login`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ id: credentials.username, password: credentials.password, }), }); if (!authResponse.ok) { return null; } const user = await authResponse.json(); return { email: user.id, name: user.nickname, image: user.image, ...user, }; }, }), ], });LoginModal.tsx"use client"; import React, { ChangeEventHandler, FormEventHandler, useState } from "react"; import styles from "./loginModal.module.css"; import { redirect, useRouter } from "next/navigation"; // client import { signIn } from "next-auth/react"; export function LoginModal() { const [id, setId] = useState<string>(""); const [password, setPassword] = useState<string>(""); const [message, setMessage] = useState<string>(""); const router = useRouter(); const onSubmit: FormEventHandler<HTMLFormElement> = async (e) => { e.preventDefault(); setMessage(""); try { await signIn("credentials", { username: id, password, redirect: false, }); router.replace("/home"); } catch (err) { console.error(err); setMessage("아이디와 비밀번호가 일치하지 않습니다."); } }; const onClickClose = () => { router.back(); }; const onChangeId: ChangeEventHandler<HTMLInputElement> = (e) => { setId(e.target.value); }; const onChangePassword: ChangeEventHandler<HTMLInputElement> = (e) => { setPassword(e.target.value); }; return ( <div className={styles.modalBackground}> <div className={styles.modal}> <div className={styles.modalHeader}> <button className={styles.closeButton} onClick={onClickClose}> <svg width={24} viewBox="0 0 24 24" aria-hidden="true" className="r-18jsvk2 r-4qtqp9 r-yyyyoo r-z80fyv r-dnmrzs r-bnwqim r-1plcrui r-lrvibr r-19wmn03" > <g> <path d="M10.59 12L4.54 5.96l1.42-1.42L12 10.59l6.04-6.05 1.42 1.42L13.41 12l6.05 6.04-1.42 1.42L12 13.41l-6.04 6.05-1.42-1.42L10.59 12z"></path> </g> </svg> </button> <div>로그인하세요.</div> </div> <form onSubmit={onSubmit}> <div className={styles.modalBody}> <div className={styles.inputDiv}> <label className={styles.inputLabel} htmlFor="id"> 아이디 </label> <input id="id" className={styles.input} value={id} onChange={onChangeId} type="text" placeholder="" /> </div> <div className={styles.inputDiv}> <label className={styles.inputLabel} htmlFor="password"> 비밀번호 </label> <input id="password" className={styles.input} value={password} onChange={onChangePassword} type="password" placeholder="" /> </div> </div> <div className={styles.message}>{message}</div> <div className={styles.modalFooter}> <button className={styles.actionButton} disabled={!id && !password}> 로그인하기 </button> </div> </form> </div> </div> ); } 혹시 같은 문제를 겪으신 분이 있었을까요? 아니면 겪으신 분 중에 해결 하신 분이 있으면 도움 부탁드립니다.
-
미해결Next + React Query로 SNS 서비스 만들기
afterlogin beforelogin 로그인 분기처리 질문
안녕하세요. 디렉토리 구조를 afterlogin과 beforelogin구조로 나누어서 로그인을 분기치고 있고 auth.ts에서 서버로 부터 전달받은 토근값을 넣고 미들웨어에서 세션을 유무를 확인하여 login페이지로 리다이렉트 시키고 있습니다. afterlogin과 beforelogin으로 디렉토리가 어떤방식으로 나뉘는지 로직이 궁금합니다. 관련된 훅이 있는것인지??2. 실제 상용화된 서비스라고하면 로그인이 풀리는것을 방지하기 위해 BE로 토근값을 요청할텐데, 관련 로직은 어떤방식으로 구현하는게 좋은방법인지 요청드립니다.
-
미해결Next + React Query로 SNS 서비스 만들기
프라이빗 컴포넌트 2개
공통으로 사용할 컴포넌트 폴더가 애프터로그인, 비포로그인 그룹 각각 폴더의_component 로 존재하는 이유가 있는건가요 ?app폴더 아래에서 비포와 애프터 둘다 공통으로 사용하게끔 뺴면 어떤 문제가 있는건가요??
-
미해결Next + React Query로 SNS 서비스 만들기
Next14
App router를 사용해서 상용화를.목표로 하는 서비스를 만들기에는 아직 무리일까요? 라이브러리가 호환이 안되는것들이 많다고 들어서 page router를 쓸지 고민이되네요 예전보다는 많이 안정화되었다고 듣기는 했는데 page router에 비해 리스크가 크지않을지 궁금합니다
-
미해결Next + React Query로 SNS 서비스 만들기
[얕은비교] Array를 props로 전달할 때
영상 항상 잘보고있습니다. 응원합니다! 질문입니다!최근에 성능최적화에 관심이 생겨서 re-rendering을 최소화하려고 관련자료를 찾아보고있는 와중에 궁금함이 생겨서 질문드립니다 ! 리액트 얕은비교는 공식문서에서 함수보면서오브젝트 1depth 까지는 for문 돌면서 값 체크를 한다고 이해했습니다. 아래 key3과 같이 값이 array이고 그안에 object 담겨잇을때 데이터 fetching 때마다key3에 담겨있는 Array의 참조값도 바뀌고 array에 담겨잇는 Object도 참조값이 매번 달라져서 rerendering이 반복적으로 일어날것 같은데 맞을가요? const data = { key1 : "aa", key3 : [{a:'a'},{b:'b'}]} 질문이 잘 전달되었으면 좋겠는데... 글로 적으니 뭔가 어렵네요.ㅠ 감사합니다.!
-
미해결Next + React Query로 SNS 서비스 만들기
MSW 오류가 발생해서 도움 부탁드립니다
서버 컴포넌트에서 Server Actions 사용하기 강의까지 그대로 따라하고 오류때문에 다시 반복해봐도 오류가 발생해서 코드 남깁니다.<SignupModal.tsx>import style from "./signup.module.css"; import { redirect, useRouter } from "next/navigation"; import { ChangeEventHandler, FormEventHandler, useState } from "react"; import BackButton from "./BackButton"; export default function SignupModal() { const submit = async (formData: FormData) => { "use server"; if (!formData.get("id")) { return { message: "no_id" }; } if (!formData.get("name")) { return { message: "no_name" }; } if (!formData.get("password")) { return { message: "no_password" }; } if (!formData.get("image")) { return { message: "no_image" }; } let shouldRedirect = false; try { const response = await fetch( `${process.env.NEXT_PUBLIC_BASE_URL}/api/users`, { method: "post", body: formData, credentials: "include", //쿠키를 사용하여 로그인 여부를 파악하기 위함 } ); console.log(response.status); // if (response.status === 403) { // return { message: "user_exists" }; // } // 이미 유저가 존재할때 403 보내주기로 약속 console.log(await response.json()); shouldRedirect = true; } catch (err) { console.log(err); } if (shouldRedirect) { redirect("/home"); //try/catch문 안에 있으면 안됨. } }; return ( <> <div className={style.modalBackground}> <div className={style.modal}> <div className={style.modalHeader}> <BackButton /> <div>계정을 생성하세요.</div> </div> <form action={submit}> <div className={style.modalBody}> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor="id"> 아이디 </label> <input id="id" name="id" className={style.input} type="text" placeholder="" required /> </div> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor="name"> 닉네임 </label> <input id="name" name="name" className={style.input} type="text" placeholder="" required /> </div> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor="password"> 비밀번호 </label> <input id="password" name="password" className={style.input} type="password" placeholder="" required /> </div> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor="image"> 프로필 </label> <input id="image" name="image" className={style.input} type="file" accept="image/*" required /> </div> </div> <div className={style.modalFooter}> <button type="submit" className={style.actionButton}> 가입하기 </button> </div> </form> </div> </div> </> ); } ><handlers.ts>http.post("/api/users", async ({ request }) => { console.log("회원가입"); // return HttpResponse.text(JSON.stringify('user_exists'), { // status: 403, // }) return HttpResponse.text(JSON.stringify("ok"), { headers: { "Set-Cookie": "connect.sid=msw-cookie;HttpOnly;Path=/", }, }); }),>이런식으로 강의와 똑같이 작성했고, 질문글중 회원가입 코드에 Path부분 빼라고 해보셔서 해봤는데 안됩니다.실행하고, 가입하기 버튼을 눌렀을때 redirect가 안되고, 헤더 200, 페이로드는 뜨지만 미리보기, 응답이 뜨지 않습니다.그리고 에러코드에 TypeError: fetch failed<cause: Error: getaddrinfo ENOTFOUND loalhost at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:118:26) at GetAddrInfoReqWrap.callbackTrampoline (node:internal/async_hooks:130:17) { errno: -3008, code: 'ENOTFOUND', syscall: 'getaddrinfo', hostname: 'loalhost' }}>이런 에러가 떠서 검색도 해봤지만 해결이 안되서 질문 드립니다.
-
해결됨Next + React Query로 SNS 서비스 만들기
유저 페이지 게시글 fetch 오류 질문
SSR 확인 중에 발견했는데, 게시글이 보이지 않는 오류가 발생해서 질문 드립니다. nest-prisma 쪽 터미널에도 오류가 따로 찍히는게 없어서혹시나해서 선생님 코드 복붙도 해봤는데 해결이 안되네요..
-
미해결Next + React Query로 SNS 서비스 만들기
MacOs, PostgreSQL16 설치, pgAdmin 4에서 connection 오류
비번 확실히 틀리지 않았는데 계속 오류 뜨길래, 완전 삭제후 다시 설치해서 비번 쉬운걸로 다시 설정하고 입력해도 계속 비번오류 뜹니다.
-
해결됨Next + React Query로 SNS 서비스 만들기
react-query 없이 동일한 동작이 가능할까요?
next의 확장된 fetch 기능만으로 동일하게 프로젝트를 만들 수 있을까요?react-query가 꼭 필요한가의 궁금증으로 시작해서 이런 글도 찾았는데,읽고 제가 내린 결론은 서버사이드 뿐만아니라 클라이언트 사이드에서의 캐싱처리를 위해 react-query를 사용한다 입니다.제가 이해한 내용이 맞을까요?
-
미해결Next + React Query로 SNS 서비스 만들기
react-query를 사용하면서 token을 header에 전달해야 할 때
이런 상황에서는 어떻게 해야할까요?강사님은 queryFn 함수를 별도로 분리하는 방식인데, 해당 함수내에서는 token을 호출할 수 없을것입니다.그렇다면 제가 생각하기에 가능한 방법은 querykey에 포함시켜서 함수에서 호출하는 방법인데...그렇게되면 token이 필요한 모든 함수의 key 배열에 토큰을 추가시켜야 하는 작업을 해야합니다.하지만 이것보다 더 좋은 방법이 있을지 잘 모르겠습니다.axios 같은 경우에는 전역적으로 header에 default로 넣는다거나 하는 방법이 있는데, fetch로 하는 경우에는 위의 방법이 최선일까요?
-
해결됨Next + React Query로 SNS 서비스 만들기
섹션1. 라우트그룹 강의중에 Hydration 에러 질문이 있습니다
안녕하세요 제로초님 강의 구매해서 따라가고 있는 수강생입니다 해당 강의에 2분44초 경을 보면 루트 레이아웃에 body안에 "루트레이아웃" 문구 넣고 localhost 3000을 보시면 정상적으로 동일하게 바뀌어 있는데 하지만 그 상태에서 브라우저 창을 새로고침을 할 경우 아래와 같은 에러가 뜨더라구요===============Hydration failed because the initial UI does not match what was rendered on the server. Warning: Expected server HTML to contain a matching text node for "루트 레이아웃" in <body>. See more info here: https://nextjs.org/docs/messages/react-hydration-error=============== 서버와 클라이언트간의 매치가 되지 않아 하이드레이션이 실패했다고 하는데 next dev를 통해 재 실행을 해도 동일합니다브라우저에서 정상적으로 텍스트 변경이 되었지만 왜 이러한 충돌이 나는걸까요 ~?
-
해결됨Next + React Query로 SNS 서비스 만들기
tanstack query 타입 지정
"use client"; import {useQuery, useQueryClient} from "@tanstack/react-query"; import {getUserPosts} from "../_lib/getUserPosts"; import Post from "@/app/(afterLogin)/_component/Post"; import {Post as IPost} from "@/model/Post"; type Props = { username: string; } export default function UserPosts({ username }: Props) { const { data } = useQuery<IPost[], Object, IPost[], [_1: string, _2: string, _3: string]>({ queryKey: ['posts', 'users', username], queryFn: getUserPosts, staleTime: 60 * 1000, // fresh -> stale, 5분이라는 기준 gcTime: 300 * 1000, }); const queryClient = useQueryClient(); const user = queryClient.getQueryData(['users', username]); console.log('user', user); if (user) { return data?.map((post) => ( <Post key={post.postId} post={post} /> )) } return null; }import {QueryFunction} from "@tanstack/query-core"; import {Post} from "@/model/Post"; export const getUserPosts: QueryFunction<Post[], [_1: string, _2: string, string]> = async ({ queryKey }) => { const [_1, _2, username] = queryKey; const res = await fetch(`http://localhost:9090/api/users/${username}/posts`, { next: { tags: ['posts', 'users', username], }, cache: 'no-store', }); // The return value is *not* serialized // You can return Date, Map, Set, etc. if (!res.ok) { // This will activate the closest `error.js` Error Boundary throw new Error('Failed to fetch data') } return res.json() } 기존 강의에서의 코드입니다. 제 생각에는 둘 다 타입을 지정해 줘야 할 필요가 있나? 라는 생각이 들어서 getUserPosts의 타입만 설정해봤는데, useQuery부분에서도 data 타입, key에 대한 타입, fn에 타입 모두 정상적으로 적용이 되는 거 같습니다. "use client"; import Post from "@/app/(afterLogin)/_component/Post"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { getUserPosts } from "../_lib/getUserPosts"; type Props = { username: string; }; export default function UserPosts({ username }: Props) { const { data } = useQuery({ queryKey: ["posts", "users", username], queryFn: getUserPosts, staleTime: 60 * 1000, // fresh -> stale, 5분이라는 기준 gcTime: 300 * 1000, }); const queryClient = useQueryClient(); const user = queryClient.getQueryData(["users", username]); console.log("user", user); if (user) { return data?.map((post) => <Post key={post.postId} post={post} />); } return null; } import { Post } from "@/model/Post"; import { QueryFunction } from "@tanstack/query-core"; export const getUserPosts: QueryFunction< Post[], [_1: string, _2: string, username:string] > = async ({ queryKey }) => { const [_1, _2, username] = queryKey; const res = await fetch(`http://localhost:9090/api/users/${username}/posts`, { next: { tags: queryKey, }, cache: "no-store", }); // The return value is *not* serialized // You can return Date, Map, Set, etc. if (!res.ok) { // This will activate the closest `error.js` Error Boundary throw new Error("Failed to fetch data"); } return res.json(); }; 이런식으로 약간 변형을 줘봤는데 기존의 코드 타입 지정과 차이가 있을까요?바꾼 부분은 UserPost에서 타입 지정은 모두 없애줬고, getUserPosts에서 next tags에 queryKey를 그대로 할당했습니다. (간혹 tags의 규칙상 queryKey에서는 가능해도 안되는 경우도 있다고 강의에서 말씀해주셨는데, 대부분의 경우에는 가능하기 때문에 대부분은 이렇게 충분하고 그런 경우에만 별도로 바꿔주면 될 거라고 생각했습니다.)
-
미해결Next + React Query로 SNS 서비스 만들기
서버 컴포넌트 fetch와 클라이언트 컴포넌트 fetch 구분
이전 질문에서 SEO가 필요없는 경우 보통 클라이언트에서 데이터를 가져오는게 더 좋다. 라는 답변을 받았습니다. 그런데 어떤게 서버에서 데이터를 가져오는거고, 어떤게 클라이언트에서 데이터를 가져오는건지가 매우 헷갈리고 있는 상황입니다. react-query를 사용할 때 query가 있는 컴포넌트에 "use client"가 있다면 클라이언트 데이터 fetch라고 보면 될까요? 그렇다면 수업 내용 중 client 컴포넌트에서 데이터를 가져온건 PostRecommends 부분이 유일한가요? 다른 react-query 부분은 모두 "use client"가 상단에 없습니다. (거기에 signup 정도...?)기존의 서버 컴포넌트에서 데이터를 가져오려고 하는데, SEO가 필요 없어 클라이언트 컴포넌트를 선택하고 싶다면, 위에 "use client"를 일부로 적어서 클라이언트 컴포넌트로 만들면 되는게 맞을까요? (데이터 페치 이전에는 함수 같은 것들이 있어서 반드시 바꿔야 되는 경우에만 "use client"를 썼기에, 필수적인게 아닌 제가 선택적으로 바꾸면 되는것인지 헷갈리네요) 컴포넌트 함수에 async를 쓰는 경우도 안쓰는 경우도 있는데, 이건 서버/클라이언트는 무관하게 단순히 async/await를 쓰기 위해 사용하는걸까요?요즘 질문이 많이 생기네요... 항상 감사합니다.
-
미해결Next + React Query로 SNS 서비스 만들기
MSW 사용 대신에 Next에서 제공하는 라우트 핸들러 사용?
https://nextjs.org/docs/app/building-your-application/routing/route-handlers궁금한 점이 있어 의견 여쭈어 보려고 글 남깁니다!공식 문서를 읽어보니 route handlers로도 백엔드를 구현할 수 있는데 MSW 사용 이유는 배포 모드일때는 라우트 핸들러를 적용한 폴더를 삭제를 해줘야하니까 번거롭기 때문에 사용하는 이유도 있다? 라고 봐도 될까요?
-
미해결Next + React Query로 SNS 서비스 만들기
chilrdren, modal의 보여지는 원리가 제가 이해한 것이 맞을까요?
안녕하세요 제로초님.인터넷창에 직접 주소를 입력하거나 새로고침 시 뒤에 배경화면이 사라지는 것에 대하여 질문있습니다. 먼저<Link href='/i/flow/signup' className={styles.signup}>를 클릭 시에는 인터셉터 라우팅이 트리거 되어 src/app/(beforeLogin)/layout.tsx에 있는 children으로 인해 배경화면에 main컴포넌트가 보여지며 인터셉터된 @modal 은 modal부분에 보여집니다. 그런데 직접 주소창에/i/flow/signup' 를 입력하여 접근하거나 새로고침하면 배경화면에 main컴포넌트가 사라지게 되는데 이것의 이유는 직접 접근할 때 인터셉에 걸리지 않게되고, childrend에는 i/flow에 있는 폴더들이 보여지며 defalut.tsx가 배경화면으로 보여지기 때문인가요?
-
미해결Next + React Query로 SNS 서비스 만들기
클라이언드 fetch와 suspense를 이용한 fetch
CSR에서의 data fetch와 suspense 안에 있는 서버컴포넌트의 data fetch는 어떤 차이가 있을까요? SSR이 아닌 CSR을 써야 하는 상황은 위처럼 데이터가 큰 상황들인데, 어차피 suspense를 이용하면 서버에서 fetch가 끝나야 렌더링이 되는게 아니라 로딩으로 되다보니 CSR 대신 suspense를 이용하면 되는거 아닌가 싶습니다. 어떤 차이가 있는지 잘 모르겠어요. CSR을 써서 더 좋은 상황이 어떤게 있을지 모르겠습니다.
-
미해결Next + React Query로 SNS 서비스 만들기
Suspence를 이용한 useSuspence(InfiniteQuery,Query) 부분 질문있습니다.
제로초님 저는 home경로에 Suspence로 감싼 <TabDeciderSuspence/>컴포넌트 안에 <PostRecommends/>, <FollowingPosts/>이 두개의 컴포넌트를 infiniteQuery 적용했습니다. 근데 useSuspenceInfiniteQuery를 적용해보려고 했는데, 적용하지 않아도 강의내용에서 말한 Suspence의 fallback 로딩이 적용되는거 같아요. Suspence가 감싼 하위 컴포넌트는 useSuspence(Query, InfiniteQuery)적용안해도 Suspence적용되나요?const Home = async () => { return ( <main className={style.main}> <TabProvider> <Tab /> <PostForm /> <Suspense fallback={<Loading />}> <TabDeciderSuspense /> </Suspense> </TabProvider> </main> ); };
-
해결됨Next + React Query로 SNS 서비스 만들기
nextjs의 hook을 사용할 때
상위 컴포넌트에서도 useSearchParam을 사용해야 하고, 하위 컴포넌트에서도 useSearchParam을 사용해야 하는 이러한 상황에서, 해당 값을 prop으로 전달할지, 독립적으로 호출할지 고민이 됩니다. 이 정도의 요소는 굳이 prop으로 전달하지 않고 다시 호출해도 유의미한 리소스 차이는 없겠죠?
-
미해결Next + React Query로 SNS 서비스 만들기
다이나믹 라우트 안의 폴더
안녕하세요. /product/[slug]/edit 이런 파일 구조를 가지고 있는데, edit 페이지에서 어떻게 하면, 현재 slug의 dynamic route 값을 가지고 올수 있을까요?edit 에서는 params가 빈 객체로 찍힙니다.미리 감사합니다!
-
해결됨Next + React Query로 SNS 서비스 만들기
서버에서 세션은 어떻게 불러오나요?
클라이언트에서는 useSession인데, 서버에서는 어떻게 불러오나요? getSession이나 getServerSession을 쓰면 되는거 같은데, 그냥 서버 컴포넌트 페이지에서 const A=await getSession()으로 해버리면, 그 다음 console.log(A)에서 pending이 나오더라구요... 서버에서 세션을 호출해서 특정 동작을 하기 위해서는 어떻게 조작해야 할까요? api에 따로 만들어서 route handler을 써야 할까요...? 이 방법도 어려움을 겪고 있긴해서 가장 좋은 방법이 뭔지 궁금합니다.