묻고 답해요
160만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨Next + React Query로 SNS 서비스 만들기
두개의 차이점
안녕하세요, 아래 ISR에 대해서 질문드렸었는데, nextjs 에서 2가지 구현 방법이있다고 해서두가지가 어떤 차이점이 있고 어떻게 활용하는게 좋을지 궁금해서 질문드립니다!1번 방법 = fetch API 의 revlidate를 활용하여 ISR 구현import ProductList from "@/component/ProductList"; import { getQueryClient } from "@/component/TanstackQueryOption"; import { getProducts } from "@/fetch/getProducts"; import { dehydrate, HydrationBoundary, QueryClient } from "@tanstack/react-query"; import Image from "next/image"; export default async function ProductPage () { const queryClient = getQueryClient(); await queryClient.prefetchQuery({ queryKey:['products'], queryFn: getProducts, }) return ( <> <section className='visual-sec'> <Image src="/visual.png" alt="visual" width={1920} height={300}/> </section> <section className="product-sec"> <h2>상품 리스트</h2> <HydrationBoundary state={dehydrate(queryClient)}> <ProductList /> </HydrationBoundary> </section> </> ) }import { isServer, QueryClient, defaultShouldDehydrateQuery, } from '@tanstack/react-query' function makeQueryClient() { return new QueryClient({ defaultOptions: { queries: { staleTime: 10 * 1000, }, dehydrate: { shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === 'pending', }, }, }) } let browserQueryClient: QueryClient | undefined = undefined export function getQueryClient() { if (isServer) { return makeQueryClient() } else { if (!browserQueryClient) browserQueryClient = makeQueryClient() return browserQueryClient } }'use client' import { getQueryClient } from '@/component/TanstackQueryOption'; import { QueryClientProvider, } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { ReactNode } from 'react' export default function TanstackQueryProvider({ children }: { children: ReactNode }) { const queryClient = getQueryClient() return ( <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools initialIsOpen={true} /> </QueryClientProvider> ) } export const getProducts = async () => { const res = await fetch(`http://localhost:9090/api/products`, { method: "GET", headers: { "Content-Type": "application/json", }, next: { revalidate: 10, } }); const data = await res.json(); const currentTime = new Date().toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3, hour12: false, }); if (typeof window === "undefined") { console.log('fetch products', 'server', currentTime); console.table(data); } else { console.log('fetch products', 'client', currentTime); console.table(data); } if(!res.ok) { throw new Error("Failed to fetch products"); } return data; }2번 방법 = export const revalidate 로 시간 설정 후 fetch로 받은 data 값을 바로 렌더링 시키기 import Product from "@/component/Product"; import styles from '@/component/ProductList.module.css'; import Image from "next/image"; export const revalidate = 10; export default async function Product2Page() { const data = await fetch('/api/products'); const products = await data.json(); console.log(revalidate, 'Product2Page'); return ( <> <section className='visual-sec'> <Image src="/visual.png" alt="visual" width={1920} height={300}/> </section> <section className="product-sec"> <h2>상품 리스트</h2> <div className={styles.productList}> {products.map((product: any) => ( <Product key={product.item_no} product={product} /> ))} </div> </section> </> ) } 2개다 ISR로 구현되며, 1번 방법은 Data Cache 캐싱 매커니즘을 활용하고,2번 방법은 Full Router Cache 캐싱 매커니즘을 활용한다의 차이점으로 생각이 드는데,이 외에 다른 차이점과 실제 개발을 하면서 선호되는 방식이 따로 있을까요? 해당 페이지만 보았을때는 2번 방법으로 해도 어차피 주기적으로 다시 생성해서 최신 데이터를 반영하면 복잡하게 react-query를 사용하고, Hydration을 하면서 데이터를 동기화할 필요가 있나 싶어서 어떻게 사용해야할지 감이 안잡히네여
-
해결됨Next + React Query로 SNS 서비스 만들기
ISR 테스트 중 궁금점
// src/components/TanstackQueryOption.ts import { isServer, QueryClient, defaultShouldDehydrateQuery, } from '@tanstack/react-query' function makeQueryClient() { return new QueryClient({ defaultOptions: { queries: { staleTime: 15 * 1000, }, dehydrate: { shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === 'pending', }, }, }) } let browserQueryClient: QueryClient | undefined = undefined export function getQueryClient() { if (isServer) { return makeQueryClient() } else { if (!browserQueryClient) browserQueryClient = makeQueryClient() return browserQueryClient } }// src/components/TanstackQueryProvider.tsx 'use client' import { getQueryClient } from '@/component/TanstackQueryOption'; import { QueryClientProvider, } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { ReactNode } from 'react' export default function TanstackQueryProvider({ children }: { children: ReactNode }) { const queryClient = getQueryClient() return ( <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools initialIsOpen={process.env.NEXT_PUBLIC_MODE === 'local'} /> </QueryClientProvider> ) } // src/app/layout.tsx import Banner from "@/component/Banner"; import Footer from "@/component/Footer"; import Header from "@/component/Header"; import TanstackQueryProvider from "@/component/TanstackQueryProvider"; import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "@/app/global.css"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en"> <body> <TanstackQueryProvider> <div className='container'> <Banner/> <Header/> <main>{children}</main> <Footer/> </div> </TanstackQueryProvider> </body> </html> ); } // src/app/page.tsx import ProductList from "@/component/ProductList"; import { getQueryClient } from "@/component/TanstackQueryOption"; import { getProducts } from "@/fetch/getProducts"; import { dehydrate, HydrationBoundary, QueryClient } from "@tanstack/react-query"; import Image from "next/image"; export default function Page () { const newQueryClient = getQueryClient(); newQueryClient.prefetchQuery({ queryKey:['products'], queryFn: getProducts, }) return ( <> <section className='visual-sec'> <Image src="/visual.png" alt="visual" width={1920} height={300}/> </section> <section className="product-sec"> <h2>상품 리스트</h2> <HydrationBoundary state={dehydrate(newQueryClient)}> <ProductList /> </HydrationBoundary> </section> </> ) }; 'use client' // src/components/ProductList.tsx import Product from "@/component/Product"; import { getProducts } from "@/fetch/getProducts"; import { useQuery, useSuspenseQuery } from "@tanstack/react-query"; import styles from "@/component/ProductList.module.css"; export const ProductList = () => { const {data, isLoading, isFetching} = useSuspenseQuery({queryKey: ['products'], queryFn: getProducts}); console.log(`isLoading: ${isLoading}, isFetching: ${isFetching}`) return ( <div className={styles.productList}> {data?.map((product: any) => ( <Product key={product.item_no} product={product} /> ))} </div> ) }; export default ProductList;// src/components/Product.tsx import Link from "next/link"; import Image from "next/image"; export const Product = ({product} : any) => { return ( <Link href={`/product/${product.item_no}`} prefetch> <Image src={product.detail_image_url} alt={product.item_name} width={500} height={300} /> <h3>{product.item_name}</h3> <span>{product.price}</span> </Link> ) } export default Product;// src/app/product/[id]/page.tsx export default function ProductDetailPage() { return ( <> 상품 상세페에지 </> ) }// src/fetch/getProducts.ts export const getProducts = async () => { const res = await fetch(`http://localhost:9090/api/products`, { method: "GET", headers: { "Content-Type": "application/json", }, next: { revalidate: 10, } }); const currentTime = new Date().toLocaleTimeString(); const data = await res.json(); if (typeof window === "undefined") { console.log('fetch products', 'server', currentTime); console.table(data); } else { console.log('fetch products', 'client', currentTime); console.table(data); } if(!res.ok) { throw new Error("Failed to fetch products"); } return data; }// src/server/server.js import express from "express"; import cors from "cors"; const app = express(); const port = 9090; app.use(cors()); app.use(express.json()); app.get("/api/products", (req, res) => { const currentTime = new Date().toLocaleTimeString(); console.log(`Received request at ${currentTime}`); const products = [ { item_no: 122997, item_name: '상품 1', detail_image_url: 'https://picsum.photos/id/237/500/500', price: 75000, }, { item_no: 768848, item_name: '상품 2', detail_image_url: 'https://picsum.photos/id/238/500/500', price: 42000, }, { item_no: 552913, item_name: '상품 3', detail_image_url: 'https://picsum.photos/id/239/500/500', price: 240000, }, // { // item_no: 1045738, // item_name: '상품 4', // detail_image_url: // 'https://picsum.photos/id/240/500/500', // price: 65000, // }, ]; res.json(products); }); app.listen(port, () => console.log('Server is running')); 안녕하세요, fetch와 tanstackQuery를 사용해서 ISR 동작을 테스트하고있었습니다.테스트 마다 .next 파일은 지우고 새로 build 하여 run start를 통하여 확인하였습니다.staleTime과 revalidate 의 시간이 서로 상이한데, 동일하게 설정했을때, 시간의 간격을 두었을때의 차이점을 직접 확인하려고 하였는데 어떤점에서 차이가 나는지 보고도 이해가 안가서 질문드립니다.궁금점 1. staleTime과 revalidate 는 gcTime 처럼 staleTime이 revalidate보다 적은 시간으로 설정을 해야하는지? 그렇다면 그 이유는 gcTime보다 작게 설정하는 이유와 같은지? 가 궁금합니다.2. server.js에 주석처리해놓은 item을 다시 주석을 해지하면 처음 revaildate의 10초 설정으로 인해새로고침을해도 아이템은 계속 페이지에서 3개만 노출되고있고, 상품을 클릭해서 이동을 하면서staleTime의 설정인 15초가 되었을때는 client 요청이 발생하여 아이템이 4개로 잘 노출되고있습니다.하지만 이 때 새로고침을 하게되면 처음 fetch revalidate로 cache되어있던 데이터인 아이템 3개까지만 노출이 되고 새로고침을 한번 더 진행해야 그때서야 4개로 노출이되는데 클라이언트와 서버 쪽이 서로 싱크가 안맞는거같은데 이러한 문제점이 왜 일어나는지 이해가 잘안됩니다!3. 확장된 fetch와 tanstackQuery를 어떻게 분리해서 사용해야할까도 많이 고민이 되는데.. queryFn 에 이미 fetch로 만들어둔 함수를 가져와 사용하니 분리라는 개념을 생각하면 안되는걸까요? fetch를 독립적으로 사용하는 경우도있다고하는데 이 경우는 왜 독립적으로 사용하는지 잘모르겠습니다.
-
미해결Next + React Query로 SNS 서비스 만들기
메인페이지 새로고침할때 hydration 오류
로그인하고 홈 메인페이지에서 새로고침하면 오류 뜹니다PostForm.tsx쪽 TextAreaAutosize 라이브러리 이 부분 주석하면 에러 안뜨는데 라이브러리 문제인지 제가 잘못한건지 모르겠습니다 key값 없다고 하는데 TrendSection.tsx에 {data?.map((trend) => ( <Trend trend={trend} key={trend.tagId} /> ))}여기 trend.tagId에서 tagId를 못찾는거 같은데 백엔드 문제인가요???타입스크립트 Hashtag에 tagId: number 있어요
-
미해결Next + React Query로 SNS 서비스 만들기
tanstack-query prefetchQuery 질문
안녕하세요 강의를 듣는중 추가적으로 tanstack-query를 공부하다가 혼자서 도저히 이해를 할수 없는 부분이 있어서 이 부분에 대해 혹시 조언을 받을수 있을까 싶어 문의드립니다. prefetchQuery가 개인적으로 잘 이해가 안되어서 별도로 프로젝트를 생성하여 기본적인 것부터 다시 공부하고 있었습니다만, tanstack-query 공식사이트에서 권장하던 방법대로 임의적으로 코드를 생성하였더니 router.push()로 다른 페이지에 갔다가(->홈으로[/]) 다시 돌아오는것(->Post페이지(/post))을 반복하다보면 가끔 서버 컴포넌트에 있는 prefetchQuery안의 fetch와 클라이언트 컴포넌트에 있는 useQuery의 fetch가 동시에 실행이 되는 일이 가끔 발생을 해서요. fetch가 이중으로 실행이 되고 있는것 같은데 아무리 코드를 살펴봐도 제가 잘못한 부분을 찾을수가 없어서 조언을 구합니다ㅠ page.tsximport { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'; import Post from './_component/Post'; import getPostRecommends from './_hook/fetch'; export default async function tanstackQuery() { const queryClient = new QueryClient(); await queryClient.prefetchQuery({ queryKey: ['movies'], queryFn: getPostRecommends, }); const dehydratedState = dehydrate(queryClient); return ( <HydrationBoundary state={dehydratedState}> <Post /> </HydrationBoundary> ); } post.tsx'use client'; import { useQuery } from '@tanstack/react-query'; import getPostRecommends from '../_hook/fetch'; import { useRouter } from 'next/navigation'; export default function TanstackQuery() { const { data } = useQuery({ queryKey: ['movies'], queryFn: getPostRecommends, }); const router = useRouter(); type PostItem = { id: number; title: string; }; return ( <div> <button onClick={() => router.push('/')}>홈으로</button> {data?.map((item: PostItem) => { return ( <div key={item.id}> <h2>{item.title}</h2> </div> ); })} {data?.message} </div> ); } getPostRecommendsexport default async function getPostRecommends() { if (typeof window === 'undefined') { console.log('서버에서 fetch 실행' + new Date()); } else { console.log('클라이언트에서 fetch 실행' + new Date()); } const response = await fetch('https://jsonplaceholder.typicode.com/posts?_page=1&_limit=10', { cache: 'no-store', }); if (!response.ok) { throw new Error('Failed to fetch data'); } const res = await response.json(); return res; } 기본 provider 설정'use client'; import { isServer, QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactNode } from 'react'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; function makeQueryClient() { return new QueryClient({ defaultOptions: { queries: { staleTime: 6 * 1000, }, }, }); } type Props = { children: ReactNode }; let browserQueryClient: QueryClient | undefined = undefined; function getQueryClient() { if (isServer) { return makeQueryClient(); } else { if (!browserQueryClient) browserQueryClient = makeQueryClient(); return browserQueryClient; } } export default function Providers({ children }: Props) { const queryClient = getQueryClient(); return ( <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools /> </QueryClientProvider> ); } 서버 콘솔 브라우저 콘솔 위에 캡쳐화면 같이 fetch가 거의 동시간에 발생을 하고 있는 모습입니다. next.js router cache 때문에 30초마다 서버 컴포넌트쪽이 리랜더링 되어서 페이지를 새로고침을 하지 않고 router.push로 다시 페이지에 들어가도 서버 컴포넌트쪽이 다시 실행된다는건 이해를 했는데, 그렇다면 초기 랜더링할때와 똑같이 데이터가 prefetch되어서 클라이언트쪽 useQuery가 실행이 되지 않아야하지 않나요? 왜 두번이나 fetch가 도는건지 아무리 자료를 찾아봐도 잘 모르겠어서 결국 문의드리게 되었습니다ㅠ
-
미해결Next + React Query로 SNS 서비스 만들기
useFormState, useFormStatus 관련 질문
[섹션4-4 클라이언트 컴포넌트에서 ServerActions 사용하기]제가 사용하고있는 라이브러리 버전입니다. "next": "^15.0.0-canary.181", "next-auth": "^5.0.0-beta.22", "react": "^18", "react-dom": "^18" useFormState현재 공식문서에선 useFormState from 'react-dom'이 아닌 useActionState from 'react` 로 사용하도록 되어있더라구요. 그래서 해당 변경사항대로 사용해도 문제가 없을지 궁금합니다. useFormStatus공식문서를 읽어보는데 "useFormStatus는 동일한 컴포넌트에서 렌더링한 <form>에 대한 상태 정보를 반환하지 않습니다."라고 명시가 되어있더라구요. 그런데 현재 제로초님의 코드는 동일한 컴포넌트의 form에 대해서 pending 값을 받고 있는데, 문제가 발생하지 않는 이유에 대해서 궁금합니다.
-
미해결Next + React Query로 SNS 서비스 만들기
prefetch 질문
안녕하세요!저는 fetch를 사용하지않고 axios를 사용해봤는데요!서버에서 prefetch한 데이터가 초기에 useQuery에 값이 없는 이슈가 있어서 디깅을 해보다가 ReactQueryProvider에서 선언한 queryClient와 Home.tsx에서 선언한 queryClient가 달라 캐시한 값을 가져오지 못한다고 생각하는데 혹시 맞을까요? "use client"; import { useState } from "react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { isProduction } from "@/app-src/shared/env"; export const ReactQueryProvider = ({ children }: React.PropsWithChildren) => { const [queryClient] = useState( () => new QueryClient({ defaultOptions: { queries: { staleTime: Infinity, gcTime: Infinity, }, }, }) ); return ( <QueryClientProvider client={queryClient}> {children} {!isProduction && <ReactQueryDevtools initialIsOpen={false} />} </QueryClientProvider> ); }; import { PostIndex } from "@/app-src/post/[slug]"; import { API_PATH } from "@/app-src/shared/apis"; import { getPost } from "@/app-src/shared/apis/post/get-post"; import { dehydrate, HydrationBoundary, QueryClient, } from "@tanstack/react-query"; const PostPage = async ({ params }: { params: { slug: string } }) => { const { slug } = params; const queryClient = new QueryClient(); // 서버에서 미리 데이터를 패칭 await queryClient.prefetchQuery({ queryKey: [API_PATH.POST(slug)], queryFn: () => getPost(slug), }); const a = queryClient.getQueryData([API_PATH.POST(slug)]); console.log(a, "a"); // 데이터 직렬화 const dehydratedState = dehydrate(queryClient); return ( <HydrationBoundary state={dehydratedState}> <PostIndex /> </HydrationBoundary> ); }; export default PostPage;"use client"; import { API_PATH } from "@/app-src/shared/apis"; import { getPost } from "@/app-src/shared/apis/post/get-post"; import { useQuery } from "@tanstack/react-query"; import { useParams } from "next/navigation"; export const PostIndex = () => { const { slug } = useParams<{ slug: string }>(); const { data } = useQuery({ queryKey: [API_PATH.POST(slug)], queryFn: () => getPost(slug), }); // 초기에 없음 -> 패칭 -> 있음 console.log(data, "data"); return ( <div> <div>{data?.title}</div> </div> ); };만약 제가 생각한게 맞다면 어떻게 해결해야할까요?
-
해결됨Next + React Query로 SNS 서비스 만들기
postgreSQL에 register server 할때마다 zcom db가 같이 생성됩니다
전공자따라잡기 데이터베이스 공부중에 mysql 대신 이 강의 할때 깔았던 postgreSQL로 연습해보려고 하다가 질문드립니다서버그룹 우클릭 -> register -> server 하면 사용자 이름으로 된 기본db와 함께 zcom이 항상 같이 생성되는데 이 zcom db 생성안되게 하려면 어떻게 해야하나요?
-
미해결Next + React Query로 SNS 서비스 만들기
Cannot read properties of undefined (reading 'substring')
강의를 따라하는 도중 이런 에러를 만났습니다 에러 경로를 보고 찾을려고 하는데 node_modules\oidc-token-hash\lib\shake256.js (3:1)가 어딘지를 잘 못찾겠습니다
-
해결됨Next + React Query로 SNS 서비스 만들기
인터셉트 + 패러렐 라우트 catch-all route 모달 닫힘 경로 문제
안녕하세요 제로초님! 제로초님 강의 보면서 개인 프로젝트 해보고 있는 수강생입니다. 현재 로그인과 회원가입 페이지를 인터셉트와 패러렐 라우트를 이용했는데,(저의 현재 폴더 구조입니다.)app/ ├── layout.tsx ├── page.tsx // 메인 페이지 ├── @modal/ │ ├── (.)login/ │ │ │ └── page.tsx // 로그인 모달 페이지 │ ├── (.)signup/ │ │ │ └── page.tsx // 회원가입 모달 페이지 // 프로필 수정 모달 페이지 │ ├── [...catchAll]/ │ │ └── page.tsx // 모달을 닫기 위한 catch-all 라우트 ├── (auth)/ │ ├── login/ │ │ └── page.tsx // 로그인 페이지 │ ├── signup/ │ │ └── page.tsx // 회원가입 페이지 └── 기타 페이지들... 회원가입이 되면 메인 페이지('/')로 이동하게 했는데 router.push, replace로는 경로는 이동이 되나 모달이 닫히지 않는 문제가 있었습니다. 그래서 catch-all route를 이용해서 특정 경로에 매칭되지 않으면 모달이 닫히도록 null을 반환하였습니다.// catch-all route export default function ModalCatchAll() { console.log('CatchAll triggered'); return null; } 그런데 router.push('/') 메인 홈페이지인 '/' 경로로 이동을 해도 catch-all route가 실행되지 않았는데, 메인 경로 외에 게시글 라우트로 router.push('/foster') 경로를 변경했더니 catch-all route가 실행이 되었습니다. router.back 말고도 push나 replace를 사용하고 싶어서 catch-all route를 사용했는데메인 홈 경로는 안되고 다른 경로로 이동시킬 때는 catch-all route가 왜 적용되는지 모르겠어서 질문 남깁니다..
-
미해결Next + React Query로 SNS 서비스 만들기
next auth error
// auth.ts import 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({ credentials: { username: { label: "Username", type: "text", placeholder: "jsmith" }, password: { label: "Password", type: "password" }, }, async authorize(credentials) { const authResponse = await fetch( `${process.env.NEXT_PUBLIC_BASE_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, }; }, }), ], }); // route.ts export { GET, POST } from "@/auth"; 각종 커뮤니티 글을 보긴했지만,, 해당 에러는 보이지 않아서 문의 남깁니다. 서버쪽 에러는 [auth][error] CallbackRouteError: Read more at https://errors.authjs.dev#callbackrouteerror[auth][cause]: TypeError: fetch failed at Object.fetch (node:internal/deps/undici/undici:11372:11) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async Object.authorize (webpack-internal:///(rsc)/./src/auth.ts:31:38) at async Module.callback (webpack-internal:///(rsc)/./node_modules/@auth/core/lib/actions/callback/index.js:241:39) at async AuthInternal (webpack-internal:///(rsc)/./node_modules/@auth/core/lib/index.js:66:24) at async Auth (webpack-internal:///(rsc)/./node_modules/@auth/core/index.js:127:34) at async /Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:6:55759 at async eO.execute (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:6:46527) at async eO.handle (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:6:57093) at async doRender (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/base-server.js:1345:42) at async cacheEntry.responseCache.get.routeKind (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/base-server.js:1555:40) at async DevServer.renderToResponseWithComponentsImpl (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/base-server.js:1475:28) at async DevServer.renderPageComponent (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/base-server.js:1901:24) at async DevServer.renderToResponseImpl (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/base-server.js:1939:32) at async DevServer.pipeImpl (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/base-server.js:914:25) at async NextNodeServer.handleCatchallRenderRequest (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/next-server.js:272:17) at async DevServer.handleRequestImpl (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/base-server.js:810:17) at async /Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/dev/next-dev-server.js:339:20 at async Span.traceAsyncFn (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/trace/trace.js:154:20) at async DevServer.handleRequest (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/dev/next-dev-server.js:336:24) at async invokeRender (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/lib/router-server.js:173:21) at async handleRequest (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/lib/router-server.js:350:24) at async requestHandlerImpl (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/lib/router-server.js:374:13) at async Server.requestListener (/Users/kimyoungwoong/Desktop/next-x/node_modules/next/dist/server/lib/start-server.js:141:13)[auth][details]: { "code": "ECONNREFUSED", "provider": "credentials"}이렇게 발생하고 있습니다. 신 버전인 만큼 .env AUTH_SECRET=woong작성 해놨고 .env local 같은 경우는 NEXT_PUBLIC_API_MOCKING=enabledNEXT_PUBLIC_BASE_URL=http://localhost:9090작성해놨습니다. 잘 해결이 안되서 글 남깁니다.
-
미해결Next + React Query로 SNS 서비스 만들기
넥스트 프론트 서버에 로그인이 필요한 이유가 궁금합니다!
안녕하세요CSR 경험만 있다보니, 프론트 서버에 로그인한다는 개념이 뭔가 와닿지가 않아서요백엔드 서버에 쿠키로 인증이 되는데, 왜 프론트 서버에 로그인하는게 필요할까요?클라이언트 사이드만 있을 때랑 뭔가 다른 느낌이네요..ㅠ
-
미해결Next + React Query로 SNS 서비스 만들기
login페이지에서 i/flow/login으로 인터셉팅 라우팅 할 때 동작은 하는데 console창에 에러가 뜹니다.
login버튼을 클릭했을 때 처음에 login페이지에 갔다가 replace로 인해서 i/flow/login으로 갈 때 인터셉팅 라우팅이 적용되어 페러렐 라우트를 사용한 (.)i/flow/login으로 이동하게 되는것 까지 이해하고 적용을 시켰고 이동은 잘 하지만 아래와 같은 에러가 발생합니다.아래는 저의 LoginModal.tsx 코드입니다."use client"; import { useState } from "react"; import style from "@/app/(beforeLogin)/_component/login.module.css"; import { useRouter } from "next/navigation"; export default function LoginModal() { const [id, setId] = useState(); const [password, setPassword] = useState(); const [message, setMessage] = useState(); const router = useRouter(); const onSubmit = () => {}; const onClickClose = () => { router.back(); }; const onChangeId = () => {}; const onChangePassword = () => {}; 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> ); } 저의 폴더 구조는 아래와 같습니다.
-
미해결Next + React Query로 SNS 서비스 만들기
cookie samesite
login 시 let setCookie = response.headers.get('Set-Cookie'); 가져오려고 할 때 response.headers에는 뜨는데 samesite 때문에 cookie를 가져올 수 없습니다. 개발자도구에서 samesite 에러 어떻게 해결할 수 있을까요??아래와 같이 수정했는데 같은 오류가 발생합니다. if (setCookie) { const parsed = cookies.parse(setCookie); cookies().set('connect.sid', parsed['connect.sid'], { sameSite: 'None', secure: true }); }
-
미해결Next + React Query로 SNS 서비스 만들기
img에 display: none을 주고 div에 background-img를 주는 이유
안녕하세요 제로초님. 강의 잘 보고 있습니다. <img src={photo.link} alt={photo.Post?.content} /><div className={style.image} style={{backgroundImage: `url(${photo.link})`}} />여기서 css를 보니 img에 display none을 주고 div에 style의 bg로 이미지를 주셨던데 왜 이렇게 하신건지 특별한 이유가 있나요? 궁금합니다.
-
미해결Next + React Query로 SNS 서비스 만들기
패러랠 라우팅 client-side 에러가 뜹니다.
병렬 라우팅에 문제가 있는 거 같아 문의 드립니다.강의 그대로 패러렐 라우트, 인터셉터 라우트까지 모두 따라했었는데,"Application error: a client-side exception has occurred (see the browser console for more information)."브라우저에 이런 에러가 뜨면서, 콘솔창에는 아래와 같이 에러가 뜨더라구요.app-index.js:33 Warning: Cannot update a component (`HotReload`) while rendering a different component (`Router`). To locate the bad setState() call inside Router, follow the stack trace as described in https://reactjs.org/link/setstate-in-render at Router (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js:207:11) at ErrorBoundaryHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:113:9) at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:160:11) at AppRouter (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js:585:13) at ServerRoot (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-index.js:112:27) at Root (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-index.js:117:11)window.console.error @ app-index.js:33Show 1 more frameShow lessUnderstand this errorreact-dom.development.js:9126 Uncaught TypeError: initialTree is not iterable at applyPatch (apply-router-state-patch-to-tree.js:17:53) at applyRouterStatePatchToTree (apply-router-state-patch-to-tree.js:74:30) at applyRouterStatePatchToTree (apply-router-state-patch-to-tree.js:76:30) at eval (navigate-reducer.js:142:88)Understand this errorapp-index.js:33 The above error occurred in the <Router> component: at Router (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js:207:11) at ErrorBoundaryHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:113:9) at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:160:11) at AppRouter (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js:585:13) at ServerRoot (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-index.js:112:27) at Root (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-index.js:117:11)React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundaryHandler. 그래서 어디서 난 에러인지 추리려고 인터셉터 라우트 부분은 아예 빼버리고,패러랠 라우트 부분으로 확인해보는데, 패러랠 라우트 부분부터 아예 에러가 나서 이것이 문제인 거 같더라구요. { "name": "z-com", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "classnames": "^2.5.1", "dayjs": "^1.11.13", "next": "14.2.11", "react": "^18", "react-dom": "^18" }, "devDependencies": { "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10.4.20", "eslint": "^8", "eslint-config-next": "14.2.11", "postcss": "^8.4.47", "tailwindcss": "^3.4.12", "typescript": "^5" } } package.json과 디렉토리 구조는 이런 상태이구요.@modal/compose/tweet/page.tsx@modal/compose/tweet/modal.module.css@modal/default.tsx이 세 파일은 깃헙 ch2-2 에 나온대로 그대로 적었습니다. import {ReactNode} from "react"; import style from '@/app/(afterLogin)/layout.module.css'; import Link from "next/link"; import Image from "next/image"; import ZLogo from '../../../public/zlogo.png'; import NavMenu from "@/app/(afterLogin)/_component/NavMenu"; import LogoutButton from "@/app/(afterLogin)/_component/LogoutButton"; import TrendSection from "@/app/(afterLogin)/_component/TrendSection"; import FollowRecommend from "@/app/(afterLogin)/_component/FollowRecommend"; // import RightSearchZone from "@/app/(afterLogin)/_component/RightSearchZone"; type Props = { children: ReactNode, modal: ReactNode } export default function AfterLoginLayout({ children, modal }: Props) { return ( <div className={style.container}> <header className={style.leftSectionWrapper}> <section className={style.leftSection}> <div className={style.leftSectionFixed}> <Link className={style.logo} href="/home"> <div className={style.logoPill}> <Image src={ZLogo} alt="z.com로고" width={40} height={40} /> </div> </Link> <nav> <ul> <NavMenu /> </ul> <Link href="/compose/tweet" className={style.postButton}> <span>게시하기dd</span> {/* <svg viewBox="0 0 24 24" aria-hidden="true" className="r-jwli3a r-4qtqp9 r-yyyyoo r-1472mwg r-dnmrzs r-bnwqim r-1plcrui r-lrvibr r-lrsllp"><g><path d="M23 3c-6.62-.1-10.38 2.421-13.05 6.03C7.29 12.61 6 17.331 6 22h2c0-1.007.07-2.012.19-3H12c4.1 0 7.48-3.082 7.94-7.054C22.79 10.147 23.17 6.359 23 3zm-7 8h-1.5v2H16c.63-.016 1.2-.08 1.72-.188C16.95 15.24 14.68 17 12 17H8.55c.57-2.512 1.57-4.851 3-6.78 2.16-2.912 5.29-4.911 9.45-5.187C20.95 8.079 19.9 11 16 11zM4 9V6H1V4h3V1h2v3h3v2H6v3H4z"></path></g></svg> */} </Link> </nav> <LogoutButton /> </div> </section> </header> <div className={style.rightSectionWrapper}> <div className={style.rightSectionInner}> <main className={style.main}>{children}</main> <section className={style.rightSection}> {/* <RightSearchZone /> */} <TrendSection /> <div className={style.followRecommend}> <h3>팔로우 추천</h3> <FollowRecommend /> <FollowRecommend /> <FollowRecommend /> </div> </section> </div> </div> {modal} </div> ) }layout.tsx도 위에 코드와 같이 거의 그대로 적었구요. 참고로 layout.tsx에서 console.log(modal) 했을 때, undefined 뜹니다.근데 또 explore로 이동한 뒤 게시하기 버튼을 누르면 modal이 뜹니다.이것 저것 다해보다가 도저히 안풀려서 문의드립니다.제가 어디서 놓친 걸까요...
-
미해결Next + React Query로 SNS 서비스 만들기
next-auth ./auth.ts 질문입니다 !
import NextAuth from "next-auth" import CredentialsProvider from "next-auth/providers/credentials"; import {NextResponse} from "next/server"; 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.NEXT_PUBLIC_BASE_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() console.log('user', user); return { email: user.id, name: user.nickname, image: user.image, ...user, } }, }), ] });@/auth.ts 파일 코드입니다. 이런식으로 에러가 나서 에러 창을 확인해보면 전달된 매개변수의 타입이 적절치 않다고 하는데, 강의 그대로 따라가면서 깃허브 참고하면서 코드를 작성했음에도 불구하고 오류가 나옵니다..
-
해결됨Next + React Query로 SNS 서비스 만들기
그룹폴더 질문있습니다.
안녕하세요 제로초님 강의를 듣다가 문득 궁금한점이 있어서 질문드립니다.현재 폴더 구조가 app하위에 (afterLogin) 폴더와 (beforeLogin) 폴더가 있는데, 지금 강의에서는 당연한듯이 beforeLogin 폴더의 layout이 뜨고있는데요, 왜 그런건지 궁금합니다.현재 단계에서는 뭔가 로그인을 했다는 조건같은걸 아직 작성하지 않은것 같아서요 정리하자면 현재 localhost:3000의 기본 페이지가afterlogin 폴더의 layout이 아니라 beforeloign의 layout이 뜨는 이유가 궁금합니다.
-
해결됨Next + React Query로 SNS 서비스 만들기
라우팅 관련 질문 있습니다!
안녕하세요강의 잘 보고 있습니다. 한가지 질문이 있는데요! 라우팅 테스트 중에 (beforeLogin) 하위로 추가 생성되는 라우트는 Link로 접속하면 잘 되는데, 직접 URL로 치고 가면 not-found 페이지가 뜨는데어떤 이유일까요 ? 질문1)아래 이미지의 itest 폴더 인터셉팅 잘됨. 하지만 새로고침 하거나 직접 localhost:3000/itest 치고 들어가면 페이지 없음. (원래는 새로고침 시 page.tsx 내용이 보여야 하지 않나요?) 질문2)테스트겸 localhost:3000/dkwlsWK 라우트를 생성해서 직접 URL치고 들어가니 역시 없는 페이지로 뜹니다.혹시 (그룹핑) 하거나 페레럴을 사용한 것과 연관이 있을까요 ? 질문3)아래 이미지 중 @modal/(.)itest 에 default.tsx 로 하면 에러가 나더라구요! (page.tsx로 하면 괜찮지만 페이지를 찾을 수 없음)
-
해결됨Next + React Query로 SNS 서비스 만들기
fetch가 force-cache여도 qn의 invalidateQueries가 가능한가요?
안녕하세요.강의를 복습하다가 캐싱 기능에 대해 질문이 생겼습니다. Next에서 fetch의 cache: 'force-cache'를 설정하면, 데이터가 공적 캐싱으로 처리된다고 알고 있습니다. 여기서 게시판을 예로 들어보면,게시물을 새로 포스팅하면 전체 게시물 목록을 갱신해야 하기 때문에 서버에서는 cache: 'no-store'를 설정하여 항상 최신 데이터를 가져오고, 클라이언트에서는 qn을 이용해서 staleTime을 설정하여 데이터를 일정 시간 동안 캐싱하고 있습니다. "force-cache"와 "useQuery" 내 두 번의 캐싱이 발생하여 비 효율적일 수 있다고 생각되어 cache: 'no-store'를 사용하여 클라이언트에서만 캐싱하고 있었는데요. 생각해보니 fetch를 이용해서 공적으로 캐싱하는게 성능적으로 우월할거 같아서 "force-cache"로 변경하고 invalidateQueries를 이용하여 클라이언트에서 post되면 데이터를 갱신하도록 변경하고 테스트를 해보았는데 잘되는게 이해가 되질 않습니다. 서버는 초기 항상 캐싱된 데이터를 반환하기 때문에 데이터가 상이할줄 알았는데 터미널을 봐도 post하고 리스트를 갱신할때만 Api 호출하고 이후엔 캐싱된 데이터를 잘 사용합니다. 이 경우, 사용자가 데이터를 갱신하면 서버의 캐시가 자동으로 업데이트되고, 공적 캐싱을 사용하여 효율적으로 데이터를 관리할 수 있는 건가요?
-
미해결Next + React Query로 SNS 서비스 만들기
서버 액션를 통한 API 통신 시 쿠키 제어
안녕하세요. 강의를 보면서 NextAuth 없이 fetch 을 커스텀 함수로 구현 후 서버 액션에서만 호출하는 방식으로 구현해보고 싶어서 진행하고있습니다. 로그아웃 기능을 구현 중 토큰이 만료되었을때(API 호출 시 401 오류 발생으로) 쿠키를 제거하는 로직을 추가하였는데, 서버 액션에서만 가능하다는 오류가 발생합니다. 수동으로 로그아웃시에는 정상적으로 토큰이 제거되고, 정상적으로 로그인 페이지로 이동됩니다.. 정확한 상황은 페이지 진입 시 토큰 만료된 경우 middleware 에는 토큰이 있기때문에 페이지에 진입되고, API 호출 시 401 오류가 발생하고 쿠키 삭제 후 로그인 페이지로 리다이렉트되는데, 이때 middleware 상에는 토큰이 존재합니다..보통 자동 로그아웃 시 middleware 에서 쿠키 만료기간을 확인하고 제거하는 방식을 사용하는지, 저와 같이 해도 정상적으로 동작되어야하는건지 어떻게 접근해야될지 모르겠습니다 ㅠㅠ