묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨Next + React Query로 SNS 서비스 만들기
spring boot를 사용한 social login
Next.js와 Spring boot를 사용해서 소셜 로그인을 추가하려고 합니다.소셜 로그인의 경우 코드와 토큰을 프론트에서 관리하지 않는 것이 좋다고 하여 백엔드에서 모든 로직을 처리하고 있습니다. 현재 프론트에서 window.location.href = "http://localhost:8080/oauth2/authorization/google";위와 같은 경로로 백엔드로 소셜 로그인 요청을 보내고 백엔드에서는 소셜 로그인을 진행한 뒤에 유저 정보를 생성하고 JWT를 생성한 뒤 응답의 헤더에 쿠키 형태로 JWT를 넣어 response.sendRedirect("http://localhost:3000/auth/social");로 리다이렉트하고 있습니다. 프론트에서는 /auth/social 페이지에서 쿠키에 있는 JWT를 꺼내 유저 정보를 가져오는 API를 다시 호출해 유저 정보를 가져오고 있습니다.이 후 auth.js 라이브러리를 통한 session 관리를 하고자 하는데 session을 업데이트 하려면 어떻게 해야 하나요..?(session.user가 있는지 없는지를 통한 로그인 체크를 위해 사용한다고 이해했습니다.) 서버 컴포넌트에서 import { auth } from "@/auth"; 를 통해 가져온 session에 데이터를 넣어보았지만 데이터가 제대로 들어가지 않는 것 같아 조언을 구해봅니다..
-
해결됨Next + React Query로 SNS 서비스 만들기
로그인 관련 질문입니다.
https://www.inflearn.com/questions/1268586/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%90%EB%9F%AC-%EC%A7%88%EB%AC%B8%EC%9E%85%EB%8B%88%EB%8B%A4여기에서 질문했던 로그인 관련 문제입니다.지금 현재 로그인 후 로그아웃 후 다른 아이디로 재로그인시에 로그아웃 버튼에 session정보가 제대로 안들어가는 것 같습니다.처음 로그인 했을 때 로그아웃에 session 정보가 들어가고, 로그아웃 후 다른 아이디로 재로그인시에 로그아웃버튼에 로그아웃전의 계정의 정보가 들어가 있습니다. 백엔드 서버 콘솔에는 정보가 제대로 들어가 있지만, let session = await auth(); 에서 session정보가 제대로 안 받아지는건지, 아니면 캐싱된 정보가 계속 쓰이는 건지 모르겠지만, 정보가 제대로 안들어갑니다.물론 새로고침시에 정상적으로 정보가 들어가집니다.제 프로젝트에서 문제가 있는건가 싶어서, 코드를 비교해보고 next, next-auth 버전을 바꿔보기도 했지만, 해결이 안되어서, 배포된 https://z.nodebird.com/ 링크로도 해보았는데 똑같은 버그가 있는 것 같아서 질문드립니다.무엇이 문제인지 궁금합니다. 좋은 하루 보내세요!
-
미해결Next + React Query로 SNS 서비스 만들기
client side에서 useSession 값이 undefined
선생님 안녕하세요 사이드플젝하면서다시 보고있는데, 클라이언트 사이드에서 useSession데이터가 없어서 질문 드립니다. import NextAuth from "next-auth" import Credentials from "next-auth/providers/credentials" export const { handlers, signIn, signOut, auth } = NextAuth({ pages: { signIn: '/login', newUser: '/signup', }, providers: [ Credentials({ // You can specify which fields should be submitted, by adding keys to the `credentials` object. // e.g. domain, username, password, 2FA token, etc. credentials: { id: {}, password: {}, }, authorize: async (credentials) => { console.log(credentials, '-------------------credentials'); const authResponse = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/login`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(credentials), }) console.log(authResponse.ok, '-----------------------------authResponse.ok'); if (!authResponse.ok) { return null; } let user = await authResponse.json(); // return user object with the their profile data console.log(user, '--------------------------------'); return { id: user.id, email: user.id, name: user.nickname, image: user.image, ...user, } }, }), ], })먼저 auth.ts에서 유저 정보를 잘 받아와서 return해주는 것 확인하고,/profile/page.ts (서버클라이언트)/profile/_component/logoutButton (클라이언트 컴퍼넌트)에서 각각 세션을 확인해 봤습니다. (서버 컴포넌트)import { auth } from '@/auth'; const session = await auth(); console.log(session, '------------server side session'); if (!session?.user) { redirect('/login'); }(클라리언트 컴포넌트)'use client'; import { Button } from '@/components/ui/button'; import { useCallback } from 'react'; // client component에서만! import { signOut, useSession } from 'next-auth/react'; import { useRouter } from 'next/navigation'; export default function LogoutButton() { const router = useRouter(); const { data: me } = useSession(); const onLogout = useCallback(() => { signOut({redirect: false}).then(() => {router.replace('/')}); }, [router]); console.log(me, '------------client side session'); if (!me?.user) return null; return ( <Button className='w-full' onClick={onLogout}>로그아웃</Button> ) }결과로와같이 클라리언트 사이드에서는 데이터가 없더라구요.혹시 어떤부분을 좀 더 봐야할지 알 수 있을까요?그리고 useSession을 호출하면서Failed to load resource: the server responded with a status of 404 (Not Found)app-index.js:33 ClientFetchError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON at fetchData (client.js:49:22) at async getSession (react.js:109:21) at async __NEXTAUTH._getSession (react.js:270:43)session 404가 계속나오는데 제가 잘못 호출하고있는건지 궁금합니다.
-
미해결Next + React Query로 SNS 서비스 만들기
link태그를 사용하는데 페이지가 새로고침이 됩니다.
안녕하세요 선생님강의보면서 플젝을 만들다가 여쭤보고 싶은게 생겼습니다.📦(afterLogin) ┣ 📂(home) ┃ ┗ 📂_component ┃ ┃ ┣ 📜mbtiCarousel.module.css ┃ ┃ ┗ 📜MbtiCarousel.tsx ┣ 📂@modal ┃ ┣ 📂(.)promise ┃ ┃ ┗ 📂form ┃ ┃ ┃ ┗ 📜page.tsx ┃ ┣ 📂[userId] ┃ ┃ ┣ 📂_component ┃ ┃ ┃ ┗ 📜UsrCarousel.tsx ┃ ┃ ┗ 📜page.tsx ┃ ┗ 📜default.tsx ┣ 📂like ┃ ┗ 📜page.tsx ┣ 📂messages ┃ ┗ 📜page.tsx ┣ 📂profile ┃ ┗ 📜page.tsx ┣ 📂promise ┃ ┣ 📂form ┃ ┃ ┗ 📜page.tsx ┃ ┣ 📂_component ┃ ┃ ┣ 📜PromiseFormButton.tsx ┃ ┃ ┣ 📜promiseSection.module.css ┃ ┃ ┗ 📜PromiseSection.tsx ┃ ┗ 📜page.tsx ┣ 📂recommend ┃ ┣ 📂_component ┃ ┃ ┗ 📜RecommendSection.tsx ┃ ┣ 📜page.tsx ┃ ┗ 📜recommend.module.css ┣ 📂setting ┃ ┗ 📜page.tsx ┣ 📂[userId] ┃ ┗ 📜page.tsx ┣ 📂_component ┃ ┣ 📜Back.tsx ┃ ┣ 📜FriendRecommendSection.tsx ┃ ┣ 📜MainTitle.tsx ┃ ┣ 📜MbtiRecommendSection.tsx ┃ ┣ 📜Modal.tsx ┃ ┣ 📜userCard.module.css ┃ ┣ 📜UserCard.tsx ┃ ┗ 📜UserCardArticle.tsx ┣ 📜layout.module.css ┣ 📜layout.tsx ┗ 📜page.tsx이런구조에서(afterLogin) > UserCard 를 클릭하면@modal > [userId] > page.tsx가 모달창으로 뜨게 됩니다.새로고침해도 모달창이 뜨게 되어있습니다. 이게 로컬에서는 잘 작동하는데테스트하려고 vercel에 배포해서 확인했더니 url은 바뀌는데 새로고침이 되고 화면은 바뀌지 않더라구요.구글링해도 만족스런 답변이 없어서 이렇게 글을 남깁니다.return ( // <UserCardArticle user={user}> <Card className={style.userCard}> <Link href={`/${user.id}`} scroll={false} className='w-full h-full absolute top-0 left-0'> <img src={user.image[0]} className='rounded-xl h-full block w-full' alt="img" /> <div className={style.userInfo}> <h2 className='text-white font-extrabold text-xl'>{user.mbti.mbti}, 궁합 {user.mbti.score}%</h2> <h2 className='text-white font-extrabold text-xl'>{user.nickname}, {user.age}</h2> <p className='text-white font-semibold text-base'>{user.distance}km, {user.area}</p> </div> </Link> <div className='absolute bottom-3 px-3 w-full'> <Button variant={'default'} className='bg-white hover:bg-slate-50 w-full'> <UserPlus color='#000000' /> <span className='ml-2 text-black font-extrabold'>친구신청</span> </Button> </div> </Card> // </UserCardArticle> )기존 선생님처럼 UserCardArticle이란 컴포넌트를 만들어서 router.push도 해봤다가 link태그로 고쳐봤는데도 새로고침이 되어서 어떤부분을 봐야할지 조언 듣고 싶습니다.감사합니다.
-
미해결Next + React Query로 SNS 서비스 만들기
로그인후 replace가 안되는 이유?
지금 로그인 후 핸들러에서 값을 잘 주는것을 확인했고auth.ts의콘솔 부분에 값이 잘 나오는것을 확인했습니다여기서 result에서도 성공했다고 나오는데 replace가 안되네요이유가 뭘까요
-
미해결Next + React Query로 SNS 서비스 만들기
2:56 src/app/page.tsx 파일을 이동 후 not-found 페이지만 뜹니다.
다른분 질문도보고 답변도보고 수정도해보았는데 도저히 안됩니다... 폴더구조입니다. import 오류로 동영상 내용과 동일하게 ../ 를 추가 후 재 호출하면 아래와같이 나옵니다. 소스상 문제는없는것같은데.. 무엇이문제일까요..다른분질문에 AI가 라우팅문제일수있다 했는데 NEXT.CONFIG.JS에 따로 설정도없습니다.다른분질문보니 /app/(beforeLogin)/page.tsx를 넣은것은 /app/page.tsx와 같은거라고 답변이 달린것도봣는데 위 오류에서 벗어날수가없습니다..
-
미해결Next + React Query로 SNS 서비스 만들기
default.tsx를 넣는대신 modal의 타입을 ?로 하면안되나요?
modal이 없는 상황의 오류를 해결하기위해 제로초님이 default.tsx라는 파일을 소개해줬는데요, 그냥 layout.tsx에서 애초에 Probs를 정의할때 type Probs= modal?:reactNode로 하면 안될까요?
-
미해결Next + React Query로 SNS 서비스 만들기
msw patch
안녕하세요 msw patch 관련해서 여쭤보고싶은게 있습니다!제로초님 강의를 응용해서 프로젝트에 msw로 데이터들을 테스트 하고있습니다 handler에서 /users란 엔드포인트로 get요청후 데이터들을 받아온뒤 다시 수정이 필요해 patch handler를 생성했습니다. mutation을 통해서 patch 요청은 성공한거같은데 기존의 get으로 받아온 user데이터들을 수정하고 싶다면 어떻게 할수있을지 여쭤보고싶습니다 ! const mutation = useMutation({ mutationFn: async (e: FormEvent) => { e.preventDefault(); const updatedUser = { userId: product.userId, nickName: product.nickName, userFileUrl: product.userFileUrl, techStack: product.techStack, position: product.position, employmentStatus: product.employmentStatus, year: product.year, links: product.links, alarmStatus: product.alarmStatus, content: product.content, softSkill: product.softSkill, }; fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/users`, { method: "PATCH", body: JSON.stringify(updatedUser), }); }, }); import { bypass, http, HttpResponse } from "msw"; import { hotPeople, peoples, users } from "./peopleData"; export const handlers = [ http.get("/peoples/hot/:tag", () => { return HttpResponse.json(hotPeople); }), http.get("/peoples", ({ request, params }) => { return HttpResponse.json(peoples); }), http.get("/users", () => { return HttpResponse.json(users); }), http.patch("/users", async ({ request }) => { return HttpResponse.text("success?"); }), ]; export default handlers;
-
해결됨Next + React Query로 SNS 서비스 만들기
데이터 리페칭 질문이 있습니다.
안녕하세요 제로초님 강의를 듣던 중 궁금한 것이 있어서 여쭈어 봅니다. 강의에서는 데이터를 mutation으로 리페칭 후 if (queryClient.getQueryData(["posts", "recommends"])) { 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.invalidateQueries({ queryKey: ["posts", "recommends"] });이런 식으로 업데이트를 하면 어떤 차이점이 있나요 ??데이터 업데이트시 쿼리를 업데이트 하고 리페칭하는 동작은 같은 것 같은데, 강의에서와 같이 복잡한 데이터 구조를 복사 해가며 구현하는 이유가 궁금합니다.성능상의 차이가 있는 것인가요 ??
-
미해결Next + React Query로 SNS 서비스 만들기
로그인 모달창 새로고침 시 배경 메인 페이지 사라지는 현상
로그인 버튼을 클릭하면 우선 '/login' 주소로 이동했다가 'i/flow/login'으로 이동하기 때문에 이때 '/login'에서 배경이 메인 컴포넌트를 보여줘야 메인 페이지가 바탕이 되고로그인 모달 창을 띄운다는 점은 이해했습니다.따라서 app/(beforeLogin)/login/page.tsx 에서 Main 컴포넌트를 보여주도록 했습니다.export default function Login() { const router = useRouter(); router.replace("/i/flow/login"); return <Main />; } 문제는 '/i/flow/login' 에서 새로고침하면 모달 창은 그대로지만 배경은 메인 페이지가 아닙니다. 이때 아래와 코드와 같이 따로 Main 컴포넌트를 불러오면 새로고침 시, 배경은 메인 페이지로 잘 나옵니다.그런데 강의와 깃허브 코드를 보니 LoginModal 컴포넌트만 보여주고 있습니다.LoginModal 컴포넌트만 있어도 app/(beforeLogin)/page.tsx에서 Main 컴포넌트를 보여주고 있으므로app/(beforeLogin)/layout.tsx에서 Main 컴포넌트가 {children}에 할당된다고 생각했습니다.따라서 아래 코드에서 Main 컴포넌트가 없어도 배경은 메인 페이지가 나온다고 생각했습니다. 아래 코드와 같이 Main 컴포넌트가 있으면 새로고침 시, 메인 페이지가 배경이 되고 그 위에 로그인 모달창이띄워지지만 Main 컴포넌트가 없으면 새로고침 시, 메인 페이지가 빈 페이지가 나옵니다. 여기서 Main 컴포넌트를 넣어서 해결해도 되는건지 의문이 들었습니다. 아래 코드에서 Main 컴포넌트를 넣지 않으면 Main 페이지가 어떻게 배경으로 보여지는 건지 알고 싶습니다!import LoginModal from "@/app/(beforeLogin)/@modal/(.)i/flow/login/page"; import Main from "@/app/(beforeLogin)/_component/Main"; export default function Page() { return ( <> <LoginModal /> <Main /> </> ); }
-
해결됨Next + React Query로 SNS 서비스 만들기
로그인 에러 질문입니다.
안녕하세요 제로초님 강의 듣던 중 동작이 원하는대로 안되어서 질문 드립니다.로그인 후 로그아웃 후 다른 아이디로 로그인 해도 로그아웃 버튼에는 전 계정의 정보가 뜹니다.아래는 로그인 모달의 코드입니다."use client"; import style from "@/app/(beforelogin)/_component/login.module.css"; import { ChangeEventHandler, FormEventHandler, useState } from "react"; import { redirect, useRouter } from "next/navigation"; import { signIn } from "next-auth/react"; export default function LoginModal() { const [id, setId] = useState(""); const [password, setPassword] = useState(""); const [message, setMessage] = useState(""); const router = useRouter(); const onSubmit: FormEventHandler<HTMLFormElement> = async (e) => { e.preventDefault(); setMessage(""); try { const response = await signIn("credentials", { username: id, password, redirect: false, }); console.log(response, "1"); router.replace("/home"); console.log("2"); } 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={style.modalBackground}> <div className={style.modal}> <div className={style.modalHeader}> <button className={style.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={style.modalBody}> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor="id"> 아이디 </label> <input id="id" className={style.input} value={id} onChange={onChangeId} type="text" placeholder="" /> </div> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor="password"> 비밀번호 </label> <input id="password" className={style.input} value={password} onChange={onChangePassword} type="password" placeholder="" /> </div> </div> <div className={style.message}>{message}</div> <div className={style.modalFooter}> <button className={style.actionButton} disabled={!id && !password}> 로그인하기 </button> </div> </form> </div> </div> ); } 아래는 로그아웃 버튼의 코드입니다. "use client"; import { useRouter } from "next/navigation"; import style from "./logoutButton.module.css"; import { signOut } from "next-auth/react"; import { Session } from "@auth/core/types"; type Props = { me: Session | null; }; export default function LogoutButton({ me }: Props) { const router = useRouter(); const onLogout = async () => { try { const response = await signOut({ redirect: false, }); console.log("signout", response); router.replace("/"); } catch (err) { console.log(err); } }; console.log("me", me); if (!me?.user) { return null; } return ( <button className={style.logOutButton} onClick={onLogout}> <div className={style.logOutUserImage}> <img src={me.user?.image as string} alt={me.user?.email as string} /> </div> <div className={style.logOutUserName}> <div>{me.user?.name}</div> <div>@{me.user?.email}</div> </div> </button> ); } 로그아웃 버튼에는 알려주신대로 레이아웃의 const session = await auth();session을 prop으로 내려주어서 사용하였습니다.처음 test 계정으로 로그인 했을 때의 콘솔을 찍어보면이렇게 test 계정이 정상적으로 나옵니다.이후 로그아웃 후에(session token 쿠키는 정상적으로 지워진 상태입니다) test4 계정으로 다시 로그인 하면이렇게 콘솔에 me 데이터가 test로 찍혀있고 test4의 정보가 로그아웃 버튼에 있습니다. 하지만 서버쪽 콘솔은 이렇게 test4로 나옵니다.이 상태에서 페이지 새로고침을 하면 정상적으로 로그아웃 버튼에는 test4의 데이터가 다시 들어가게 됩니다.로그아웃을 누를 때 세션 토큰이 정상적으로 지워지는 것을 확인했고, 재 로그인을 했는데 서버쪽에는 test4데이터가 찍히고 클라이언트 콘솔에는 test데이터가 찍히는 이유가 궁금합니다..2. 틀린 아이디 비밀번호를 입력했을 때에 제로초님과 응답이 다르게 날라옵니다.로그인 시에 틀린 아이디 비밀번호를 입력시에 저는 401에러가 아닌 이런 응답이 날라옵니다.이 부분은 에러인지 아니면 버전이 달라서 응답이 다른 것 인지는 잘 모르겠습니다. error는 credentialSignIn으로 날라오지만 ok와 status는 true에 200으로 날라오는데, 버전차이인가요 ..? next-auth 버전은 5.0.0-beta.17 입니다.
-
미해결Next + React Query로 SNS 서비스 만들기
api호출 3초뒤로 하고, loading.tsx 파일이 있을 때 없을때 /home/page.tsx
안녕하세요 제로초님제목처럼api호출 3초뒤로 하고, loading.tsx 파일이 있을 때 없을때 /home/page.tsx이런식으로 테스트를 해봤습니다. /home/page.tsx에서는 react-query로 ssr을 적용했고import style from './home.module.scss' import Tab from "@/app/(afterLogin)/home/_component/Tab"; import TabProvider from "@/app/(afterLogin)/home/_component/TabProvider"; import PostForm from "@/app/(afterLogin)/home/_component/PostForm"; import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query'; import TabDecider from "@/app/(afterLogin)/home/_component/TabDecider"; import { getPostRecommends } from '@/app/(afterLogin)/_lib/getPostRecommends'; export default async function Home() { const queryClient = new QueryClient(); await queryClient.prefetchInfiniteQuery({ queryKey: ['posts', 'recommends'], queryFn: getPostRecommends, initialPageParam: 0, }); const dehydratedState = dehydrate(queryClient); return ( <main className={style.main}> <HydrationBoundary state={dehydratedState}> <TabProvider> <Tab /> <PostForm /> <TabDecider /> </TabProvider> </HydrationBoundary> </main> ); } /mocks/handlers.tsx에서는 postRecommends를 가져오되 3초 delay를 줬습니다.http.get('/api/postRecommends', async ({ request }) => { console.log('----------------------------------handlers /api/postRecommends'); await delay(3000); const url = new URL(request.url); const cursor = parseInt(url.searchParams.get('cursor') as string) || 0; return HttpResponse.json( [ { postId: cursor + 1, User: User[0], content: `${cursor + 1} ${faker.lorem.paragraph()}`, Images: [{ imageId: 1, link: faker.image.urlLoremFlickr({ category: 'animals' }) }], createdAt: generateDate(), }, { postId: cursor + 2, User: User[0], content: `${cursor + 2} ${faker.lorem.paragraph()}`, Images: [ { imageId: 1, link: faker.image.urlLoremFlickr({ category: 'animals' }) }, { imageId: 2, link: faker.image.urlLoremFlickr({ category: 'animals' }) }, ], createdAt: generateDate(), }, { postId: cursor + 3, User: User[0], content: `${cursor + 3} ${faker.lorem.paragraph()}`, Images: [], createdAt: generateDate(), }, { postId: cursor + 4, User: User[0], content: `${cursor + 4} ${faker.lorem.paragraph()}`, Images: [ { imageId: 1, link: faker.image.urlLoremFlickr({ category: 'animals' }) }, { imageId: 2, link: faker.image.urlLoremFlickr({ category: 'animals' }) }, { imageId: 3, link: faker.image.urlLoremFlickr({ category: 'animals' }) }, { imageId: 4, link: faker.image.urlLoremFlickr({ category: 'animals' }) }, ], createdAt: generateDate(), }, { postId: cursor + 5, User: User[0], content: `${cursor + 5} ${faker.lorem.paragraph()}`, Images: [ { imageId: 1, link: faker.image.urlLoremFlickr({ category: 'animals' }) }, { imageId: 2, link: faker.image.urlLoremFlickr({ category: 'animals' }) }, { imageId: 3, link: faker.image.urlLoremFlickr({ category: 'animals' }) }, ], createdAt: generateDate(), }, ] ); }), 이 후에 ssr화면 테스트를 위해서브라우저의 자바스크립트 사용중지 했습니다.서버도 각각 재시작하고 테스트했습니다.그리고 나서 테스트한 화면입니다.아래 화면은 loading.tsx가 업을때,아래 화면은 loading.tsx가 있을때 입니다.선생님 강의에서ssr을 적용했을때 loading화면을 포기해야한다고 이해했었습니다.prefetchInfiniteQuqery를 쓰고 loading을 포기하거나, prefetchInfiniteQuqery을 포기하고 loading을 사용하거나.그런데 제가 테스트했을때는 ssr이라도 로딩 화면이 보이는걸로 봐서 제가 테스트를 잘못한건지,loading.tsx가 있는것 자체가 suspense를 사용한것과 동일한 효과를 주어서 보여지게 되는건지여쭙고 싶습니다. 제가 잘못테스트했다면 어떻게 테스트를 해야 좀 더 정확히 구분지을 수있을지도 궁금합니다.
-
미해결Next + React Query로 SNS 서비스 만들기
The Middleware "/src/middleware" must export a middleware or a default function
미들웨어 matcher에 추가한 페이지 home으로 replace 시에 이렇게 에러가 뜨는데 이유를 모르겠습니다 ㅠ⨯ Error [ERR_HTTP_HEADERS_SENT]: Cannot append headers after they are sent to the clientat ServerResponse.appendHeader (node:_http_outgoing:689:11)at AsyncLocalStorage.run (node:async_hooks:346:14) import { auth as middleware } from "./auth"; export const config = { matcher: ["/compose/tweet", "/home", "/explore"], };
-
미해결Next + React Query로 SNS 서비스 만들기
msw 모바일 환경으로 local 접속 에러
localhost 환경에서 모바일로 접속 하였을때 IP 주소 확인하여 넣어줬는데도 api error가 발생하는데 이유를 잘 모르겠습니다.app.use( cors({ origin: 'http://내 IP 주소:3000', optionsSuccessStatus: 200, credentials: true }) );
-
미해결Next + React Query로 SNS 서비스 만들기
msw text/event-stream 질문
안녕하세요. 강의를 다 듣고 혼자 프로젝트를 진행해보고 있는 중인데 궁금한 것이 생겨 질문 드립니다!msw로 모킹하여 스트림데이터를 넘겨주고, 브라우저에 데이터가 나타날 때 gpt 답변처럼 한글자씩 보여지게 처리하고 싶은데 공식문서를 봐도 잘 안되네요..(한글자씩 안 나오고 한번에 나타남) 감사합니다.버전 : msw 2.0참고 문서 : https://mswjs.io/docs/recipes/streaming/
-
해결됨Next + React Query로 SNS 서비스 만들기
Suspense와 prefetch 관련 질문이 있습니다.
안녕하세요 제로초님! 강의를 듣던 중 궁금한게 생겨서 질문 드립니다. TabDeciderSuspense 컴포넌트를 사용하는 것을 보았습니다.원래는 postRecommends의 데이터를 프리페칭해서 서버에서 데이터를 페칭하고 클라이언트에서 하이드레이션 하고있었는데, suspense를 사용해버리면 서버에서 프리페칭이 되는건지 궁금합니다.suspense를 적용하고 나서 페이지 새로고침 후에 페이지 document를 보면이렇게 prefetch한 데이터가 있는 것이 아니라 loading이 넘어와 있습니다.원래는 서버에서 prefetch한 데이터가 document로 넘어왔는데, suspense 적용 후 로딩스피너가 넘어 온 것으로 보아 suspense를 적용하면 prefetch가 되지 않는 것 인가요 ?? 그것이 아니라면 suspense를 적용하고 prefetch를 사용하는 이유가 궁금합니다.
-
미해결Next + React Query로 SNS 서비스 만들기
next-auth 질문 + 새소식 올려주신거 참고해서 await signIn('credentials', { ...data, redirect: true });수정한후 로그인 후 홈으로 이동안됩니다.
안녕하세요 선생님새소식 올려주신거 참고해서 await signIn('credentials', { ...data, redirect: true });수정한후 로그인 후 홈으로 이동안됩니다.home 갔다가 로그인화면으로 이동해버리는데 어디서 이동시키는건지 뒤저봐도 못 찾겠더라구요. 로그인화면으로 새로고침되는듯 합니다...true만 하고 이전 코드와 다 똑같은데 조언 부탁드립니다 ㅠ/src/app/(beforeLogin)/_component/Loginmodal.tsxconst onSubmit: SubmitHandler<formProps> = async (data: formProps) => { console.log(data); try { await signIn('credentials', { ...data, redirect: true }); router.replace('/home'); console.log('---------------------------------------after LoginModal login') } catch(error) { console.error(error); console.log('아이디와 비밀번호가 일치히자 않습니다.'); } }; src/middleware.tsexport async function middleware() { const session = await auth(); console.log(session, '------------------------------middleware session') if (!session) { return NextResponse.redirect('http://localhost:3001/i/flow/login'); } } // See 'Matching Paths' below to lean more // 미들웨어를 적용할 라우트로 로그인을 해야하는 페이지 // 페이지 접근관리 하기 쉬워짐 export const config = { matcher: ['/compose/tweet', '/home', '/explore', '/messages', '/search'] } /src/auth.tsimport NextAuth, {CredentialsSignin} from "next-auth" // import CredentialsProvider from "next-auth/providers/credentials" import Credentials from "next-auth/providers/credentials" import { NextResponse } from 'next/server'; export const { // api 라우트 handlers: { GET, POST }, // auth 함수 실행하면 로그인 유무알 수 있다. auth, // 로그인 하는 함수 signIn } = NextAuth({ pages: { signIn: "/i/flow/login", newUser: '/i/flow/signup', }, providers: [ Credentials({ // You can specify which fields should be submitted, by adding keys to the `credentials` object. // e.g. domain, username, password, 2FA token, etc. credentials: { id: {}, password: {}, }, authorize: async (credentials) => { console.log('-------------------------------------------auth.ts'); const authResponse = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) }) // 여기 주목!!! 서버에서 에러가 발생할 때 그 에러 내용이 서버에 담겨 있을 겁니다. console.log(authResponse.ok, authResponse.status, authResponse.statusText) if (!authResponse.ok) { const credentialsSignin = new CredentialsSignin(); if (authResponse.status === 404) { credentialsSignin.code = 'no_user'; } else if (authResponse.status === 401) { credentialsSignin.code = 'wrong_password'; } throw credentialsSignin; } // 로그인 성공 const user = await authResponse.json(); console.log('user', user); // return user object with the their profile data return { ...user, name: user.nickname, email: user.id, } }, }), ] }) next-auth 소식이나 말쓰하시는거 들어봐도 아직 안정화가 되지 않은것같은데 선생님은 next-auth를 실무에 도입해도 된다고보시나요? 아니면 nodebird 처럼 express로 하는걸 더 추천하실지 궁금합니다. 넥스트오쓰에서 가입한 사용자를 데이터베이스로 볼수있는지도 궁금하구 어차피 배워야할것이니 이런저런 부족한게 좀 있더라도 밀고나가야하는지.. 여유되실때 확인해주시면 감사하겠습니다.
-
미해결Next + React Query로 SNS 서비스 만들기
로그인후 바로 뒤로가기, 회원가입 후 홈으로 이동하고 session에 정보 안쌓임
안녕하세요 선생님.로그인 후에 새로고침이나 url을 치고 /(메인)으로 가면 홈으로 잘 리다이렉트 되는데 로그인 후 바로 뒤로가기를 누르면 리다이렉트되지 않고 / 페이지로 이동합니다. 이 부분 어떻게 하면 좋을지 문의 드립니다. 회원가입 후303뜨면서 홈으로 이동하는데, 이 303이 괜찮은건지와이동 후에 로그아웃버튼에서 session 정보를 가져오지 못하고 있습니다. 새로고침하면 잘 나옵니다. me정보를 가져올때 useEffect로 바꿔야할지 문의 드립니다.로그를 보면 회원가입 후, 로그인도 잘 되는것 같은데 어떤부분을 확인해야할지 알려주시면 감사하겠습니다. @/app/(beforelogin)/_lib/signup.tsx'use server'; import { signIn } from '@/auth'; import { redirect } from 'next/navigation'; const onSubmit = async (prevState: any, formData: FormData) => { if (!formData.get('id') || !(formData.get('id') as string).trim()) { return { message: 'no_id' }; } if (!formData.get('name') || !(formData.get('name') as string).trim()) { return { message: 'no_name' }; } if (!formData.get('password') || !(formData.get('password') as string).trim()) { return { message: 'no_password' }; } if (!formData.get('image')) { return { message: 'no_image' }; } let shouldRedirect = false; try { console.log('-------------------------signup start'); const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/users`, { method: 'post', body: formData, credentials: 'include', // cookie 전달 위해서 }); // console.log(response); console.log(response.status); if (response.status === 403) { return { message: 'user_exists' }; } const user = await response.json(); console.log(user, '-------------------------signup'); shouldRedirect = true; // 회원가입 성공하고 로그인 시도 await signIn("credentials", { username: formData.get('id'), password: formData.get('password'), redirect: false, }) } catch (error) { console.error(error); return { message: null }; } if (shouldRedirect) { redirect('/home'); // redirect는 try/catch문에서 쓰면 안된다. } } export default onSubmit; @/auth.tsimport NextAuth from "next-auth" // import CredentialsProvider from "next-auth/providers/credentials" import Credentials from "next-auth/providers/credentials" export const { // api 라우트 handlers: { GET, POST }, // auth 함수 실행하면 로그인 유무알 수 있다. auth, // 로그인 하는 함수 signIn } = NextAuth({ pages: { signIn: "/i/flow/login", newUser: '/i/flow/signup', }, providers: [ Credentials({ // You can specify which fields should be submitted, by adding keys to the `credentials` object. // e.g. domain, username, password, 2FA token, etc. credentials: { id: {}, password: {}, }, authorize: async (credentials) => { console.log('-------------------------------------------auth.ts'); const authResponse = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) }) // console.log('authResponse-----------------------------------', authResponse); // 로그인 실패 if (!authResponse.ok) { return null } // 로그인 성공 const user = await authResponse.json(); console.log('user', user); // return user object with the their profile data return { ...user, name: user.nickname } }, }), ] }) @/app/(beforelogin)/_component/loginmodal.tsx'use client'; import style from '@/app/(beforeLogin)/_component/login.module.scss'; import { useRouter } from 'next/navigation'; import { SubmitHandler, useForm } from 'react-hook-form'; // import { signIn } from '@/auth'; // 서버환경일 때 import { signIn } from 'next-auth/react'; // 클라이언트일 때 type formProps = { id: string, password: string, } export default function LoginModal() { const { register, handleSubmit, formState: { errors } } = useForm<formProps>(); const router = useRouter(); const onClickClose = () => { router.back(); // TODO: 뒤로가기가 /home이 아니면 /home으로 보내기 }; const onSubmit: SubmitHandler<formProps> = async (data: formProps) => { console.log(data); try { await signIn('credentials', { ...data, redirect: false }); router.replace('/home'); } catch(error) { console.error(error); console.log('아이디와 비밀번호가 일치히자 않습니다.'); } }; return ( <div className={style.modalBackground}> <div className={style.modal}> <div className={style.modalHeader}> <button className={style.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={handleSubmit(onSubmit)}> <div className={style.modalBody}> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor='id'> 아이디 </label> <input id='id' className={style.input} type='text' placeholder='' {...register('id', { required: '아이디를 입력해주세요.' })} /> {errors.id?.message && typeof errors.id.message === 'string' && <p>{errors.id.message}</p>} </div> <div className={style.inputDiv}> <label className={style.inputLabel} htmlFor='password'> 비밀번호 </label> <input id='password' className={style.input} type='password' placeholder='' {...register('password', { required: '비밀번호를 입력해주세요.' })} /> {errors.password?.message && typeof errors.password.message === 'string' && <p>{errors.password.message}</p>} </div> </div> <div className={style.modalFooter}> <button className={style.actionButton}>로그인하기</button> </div> </form> </div> </div> ); } @/app/(beforelogin)/page.tsximport Main from '@/app/(beforeLogin)/_component/Main'; import { auth } from '@/auth'; import { redirect } from 'next/navigation'; export default async function Home() { console.log('--------------before login home'); const session = await auth(); if (session?.user) { redirect('/home'); return null; } return ( <> <Main /> </> ); }
-
미해결Next + React Query로 SNS 서비스 만들기
tailwind 질문입니다.
테일윈드랑 비교하셨는데욥!tailwind는 결국 프레임워크이고다른 css 는 직접 작성을 해야하니 목적이 다르다고 생각했는데요.css를 직접 작성하신 이유가있을까요??테일윈드를 쓰면 호불호가 있긴 하겠지만 강의특성상 css 를 빠르게 사용할수있다고 생각되어서요
-
미해결Next + React Query로 SNS 서비스 만들기
앱라우터를 쓰면서 컴포넌트 분류가 궁금합니다...
제로초님 강의를 보면서 새롭게 페이지 작업을 하고 있는데 최다 클라이언트 컴포넌트로 작업이 되고 잇어서 고민이 됩니다.예를들어 쇼핑몰의 상세페이지를 만드는데 좋아요나 판매하기 기타등등 여기에 써야하는 click이벤트 들때문에 서버컴포넌트를 쓰기는 애매해서 클라이언트 컴포넌트로 작업 중인데... 모든 컴포넌트들이 이런 상황입니다ㅠㅠ 혹시 클라이언트 컴포넌트와 서버컴포넌트를 나누는 기준 같은거 알수 잇을까요??