묻고 답해요
156만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결Next + React Query로 SNS 서비스 만들기
47강 팔로잉 게시글
안녕하세요 제로초님! 47강의 팔로잉 게시글은 SSR 안필요하다고 하셨는데 그 이유가 뭔가요?! 제 생각은.. 팔로우 한 것은 유저마다 달라서? ssr 자체가 말이 안돼서(?) 그런거라고 추측해봅니다.
-
미해결Next + React Query로 SNS 서비스 만들기
Post 정보 불러올 때
안녕하세요 제로초님 질문있습니다. 강의에서 post 데이터 서버로부터 불러오고 화면에 렌더링 해주는 과정을 아래와 같이 이해하였습니다. prefetchQuery 로 서버로부터 데이터를 가져온다.그렇게 가져온 데이터를 client component 에서 useQuery 로 가져온다 위와 같이 하는 이유가 궁금합니다. 사실 처음부터 클라이언트 컴포넌트로 useQuery 만 써도 문제없이 동작할텐데useQuery 이전에 prefetchQuery 로 먼저 불러오는 이유는 ssr 을 위해서이고, 그말은 즉 seo 를 위해서 인가요? (useQuery 만 사용하면 csr 방식이여서..?) 감사합니다.
-
미해결Next + React Query로 SNS 서비스 만들기
@modal/(i) 로 생성/파일이동 후, 무한 GET 출력
강의의 3분 정도 쯤에,(beforeLogin)/i 폴더와 파일들을 (beforeLogin)/@modal/(i) 로 복사 한 후http://localhost:3000/i/flow/login 접속시 무한 GET 접속 시도가 터미널 창에 보입니다.이때, <Link href="login" className={styles.login}>로그인</Link> 입니다. 그런데, <Link href="/i/flow/login" className={styles.login}>로그인</Link> 변경하면, http://localhost:3000/i/flow/login 접속시, 터미널 창에서는 무한 GET 접속시도는 사라집니다.어떻게 해서 이렇게 되는지요?
-
미해결Next + React Query로 SNS 서비스 만들기
인터셉팅 라우트 설명
강의 50초 정도에, i, login, page.tsx는 {children}에서 렌더링, 여기는 (@modal 의 하위 폴더와 파일 선택) {modal}에서 되죠? 라고 설명하시는 부분이 이해가 안됩니다.저는, app/(beforeLogin)/layout.tsx 의 {children}은 app/(beforeLogin)/page.tsx 를 가져오는 것이고, {modal}은 @modal 폴더의 page.tsx (default.tsx, i/flow/login/폴더로 리다이렉션) 를 찾는 것으로 이했습니다.여러 폴더를 모두 보여준다고 설명하신듯하여, 이해가 안되네요.
-
미해결Next + React Query로 SNS 서비스 만들기
재랜더링 최적화 문제
안녕하세요. 제가 직접 tailwind css v4를 이용해서 트위터 클론코딩을 진행하고 있는데SearchForm에서 입력이 이루어지면, SearchForm의 영향으로 인해서 다른 영역에서도 재랜더링이 일어나는 것 같습니다.먼저 묻고싶은 내용은 어떻게 하면 다른 영역은 재렌더링이 일어나지 않게 할까 입니다. 리엑트 개발자 도구로 렌더링이 일어날때 하이라이트 표시를 켰습니다. 이 때 SearchForm에 입력이 일어나면 위와같이 SearchForm만 아닌, 다른 영역에도 렌더링이 되는 모습이 보입니다. SearchForm 구성은 다음과 같이했습니다.최대한 현재 트위터의 입력 방식을 맞추어 보았습니다."use client"; import { useSearchParams } from "next/navigation"; import React, { useRef, useState, ChangeEventHandler, useEffect, memo, } from "react"; interface SearchFormProps { q?: string; } const SearchForm = () => { const [value, setValue] = useState(""); const inputRef = useRef<HTMLInputElement>(null); const searchParams = useSearchParams(); const search = searchParams.get("q"); const handleChange: ChangeEventHandler<HTMLInputElement> = (e) => setValue(e.target.value); const handleClear = () => { setValue(""); inputRef.current?.focus(); }; useEffect(() => { setValue(search ?? ""); }, []); return ( <div className="relative w-inherit h-12 group min-w-0"> {/* Border 레이어 */} <div className={` absolute inset-0 border border-default-border rounded-full transition-all duration-75 group-focus-within:border-2 group-focus-within:border-twiiter-blue `} /> {/* 내부 요소 컨테이너 */} <div className="relative w-full h-full"> <form className="flex items-center w-full h-full px-4" autoComplete="off" onSubmit={(e) => e.preventDefault()} > {/* 검색 아이콘 (외부 div 기준 위치) */} <svg className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none" width={20} height={20} viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" strokeWidth={2} > <circle cx="11" cy="11" r="8" /> <line x1="21" y1="21" x2="16.65" y2="16.65" /> </svg> {/* Input */} <input ref={inputRef} type="search" placeholder="검색" value={value} onChange={handleChange} className=" w-full h-full pl-10 pr-10 bg-transparent text-base outline-none placeholder-gray-500 appearance-none " /> {/* Clear 버튼 (외부 div 기준 위치) */} {value && ( <button type="button" onClick={handleClear} className=" absolute right-3 top-1/2 -translate-y-1/2 w-7 h-7 flex items-center justify-center bg-gray-700 hover:bg-gray-800 rounded-full transition " tabIndex={-1} aria-label="Clear" > <svg width={16} height={16} viewBox="0 0 20 20" fill="white"> <path d="M10 8.586l4.95-4.95 1.414 1.414L11.414 10l4.95 4.95-1.414 1.414L10 11.414l-4.95 4.95-1.414-1.414L8.586 10l-4.95-4.95L5.05 3.636 10 8.586z" /> </svg> </button> )} </form> </div> </div> ); }; export default SearchForm; 이를 SearchHeader에 적용하였으며.import React from "react"; import SearchTab from "./SearchTab"; import SearchForm from "@/components/common/SearchForm"; const SearchHeader = () => { return ( <div className="w-[calc(100%-72px)] sm:pl-0 sm:max-w-[598px] fixed backdrop-blur-lg border-b-1 border-default-border bg-white/70 dark:bg-black/30"> <div className="w-full mt-2"> <SearchForm /> </div> <div className="w-full mt-1 h-[53px]"> <SearchTab /> </div> </div> ); }; export default SearchHeader; 최종적으로 /search 페이지에 이를 적용했습니다.import React from "react"; import Post from "../home/_components/Post"; import SearchHeader from "./_components/SearchHeader"; interface SearchPageProps { searchParams: Promise<{ q: string; f?: string; pf?: string }>; } const SearchPage = async ({ searchParams }: SearchPageProps) => { const { q } = await searchParams; return ( <main className="main-container"> <SearchHeader /> <div className="mt-[107px]"> <Post /> <Post /> <Post /> <Post /> <Post /> <Post /> <Post /> <Post /> <Post /> </div> </main> ); }; export default SearchPage; 관련된 layout.tsx는 다음과 같습니다.import Image from "next/image"; import Link from "next/link"; import React, { ReactNode } from "react"; import logo from "@/../public/twitter-logo.svg"; import SearchForm from "@/components/common/SearchForm"; import NavMenu from "./_components/NavMenu"; import LinkButton from "@/components/common/LinkButton"; import FollowCard from "./_components/FollowCard"; import TrendCard from "./_components/TrendCard"; import RightSection from "./_components/RightSection"; interface AfterLoginLayoutProps { children: ReactNode; modal: ReactNode; } const AfterLoginLayout = ({ children, modal }: AfterLoginLayoutProps) => { return ( <div className="overflow-y-scroll"> <div className="flex items-stretch"> <header className="flex items-end flex-col sm:flex-grow-1"> <section className="w-[72px] p-2 xl:p-0 xl:w-[275px] h-dvh"> <div className="flex flex-col w-inherit h-dvh fixed "> <Link href={"/home"}> <div className="w-14 h-14 rounded-[50%] flex justify-center items-center hover:bg-[rgba(15,20,25,0.1)]"> <Image src={logo} alt={"twitter logo"} width={40} height={40} /> </div> </Link> <NavMenu /> </div> </section> </header> <div className="flex items-start flex-col h-dvh flex-grow-1"> <div className="h-full w-[100%] sm:w-[600px] lg:w-[920px] xl:w-[990px] flex justify-between "> <main className="w-full sm:max-w-[600px] min-h-full h-fit border-l-1 border-r-1 border-default-border"> {children} </main> <div className="hidden lg:block lg:w-[290px] xl:w-[350px] h-fit relative"> <div className="flex flex-col w-inherit h-dvh fixed pt-1"> <RightSection /> </div> </div> </div> </div> </div> {modal} </div> ); }; export default AfterLoginLayout; SearchForm에 React.memo를 적용을 해보아도 변하지 않았습니다.(다른 영역, 예를 들어 main이 아닌 양 옆의 영역들에 대해서도 React.memo를 넣었지만 그래도 렌더링이 일어나고 있습니다.) 재랜더링 조건은 부모 컴포넌트가 재랜더링 되거나, props가 바뀌거나, state 변경이 일어났을 때인걸로 알고 있습니다.searchForm에서 Input만 영역에만 영향을 줘야할 것이 분명한데, 전체 영역에서 재랜더링이, 즉 layout.tsx에서 재랜더링이 일으키고 있는것 같습니다. (부모 영역) 혹시 렌더링이 어디까지 일어나는지 확인하는 (즉 최종 부모 컴포넌트까지) 방법이 있나요?그리고 이런 문제가 있을때 어떻게 최적화 할 수 있나요?p.s. 코드상 w-inherit 클래스가 이면 제가 직접 tailwind css utility에 추가하여 기능하도록 만들었습니다.+ 추가console.log()로 재랜더링이 일어나는지 확인해 보았는데 콘솔이 뜨지 않아 재랜더링이 아닌 다른 무슨 활성화(?) 인것 같아 보입니다... 하지만 SearchForm 입력 가지고 무언가 활성화 되는 것 자체가 조금 이해가 되지 않아서... 일단 재랜더링이라고 표현으로 남겨두었습니다...
-
미해결Next + React Query로 SNS 서비스 만들기
msw ssr 관련 질문입니다.
안녕하세요 제로초님next 15버전으로 바뀌고나서 새로 처음부터 다시 강의를 들으면서 만들어 보고 있습니다. next 15버전으로 바뀌고 나서 옛날꺼랑 msw 설정이 바뀌었더라구요그런데 강의대로 msw 설정을 하니 ssr 적용이 제대로 안되는 것 같습니다.msw 설정 후 페이지의 network 탭에 localhost document의 미리보기 내용이 없습니다. ssr이 된다면 document에 미리보기에 내용이 넘어와야 하는데 msw 설정 후에 없습니다. 옛날에 만들어 놓았던, next14 버전으로 했었던 프로젝트에서는 미리보기에 내용이 잘 담겨 ssr이 잘되고 있고,15버전으로 진행한 프로젝트에서layout에 있는 mswprovider 컴포넌트를 없애주니 다시 미리보기에 내용이 생기는 것으로 보아 msw 설정에서 문제가 생긴 것으로 추측됩니다. 아래는 옛날 14 버전으로 진행했던 코드입니다. msw 2.1 버전입니다."use client"; import { useEffect } from "react"; export const MSWComponent = () => { useEffect(() => { if (typeof window !== "undefined") { if (process.env.NEXT_PUBLIC_API_MOCKING === "enabled") { require("@/mocks/browser"); } } }, []); return null; }; 아래는 지금 하고있는 코드입니다. msw 2.7.3 버전입니다."use client"; import { Suspense, use } from "react"; import { handlers } from "@/mocks/handlers"; const mockingEnabledPromise = typeof window !== "undefined" // browser일 때 ? import("@/mocks/browser").then(async ({ default: worker }) => { if (process.env.NODE_ENV === "production") { return; } await worker.start({ // msw가 처리할 수 없는 요청이 들어왔을 때 onUnhandledRequest(request, print) { if (request.url.includes("_next")) { // next가 내부적으로 처리하는 url이기 때문에 msw가 처리할 필요 없음 그래서 return return; } print.warning(); }, }); worker.use(...handlers); (module as any).hot?.dispose(() => { worker.stop(); }); console.log(worker.listHandlers()); }) : Promise.resolve(); export function MSWProvider({ children, }: Readonly<{ children: React.ReactNode; }>) { // If MSW is enabled, we need to wait for the worker to start, // so we wrap the children in a Suspense boundary until it's ready. return ( <Suspense fallback={null}> <MSWProviderWrapper>{children}</MSWProviderWrapper> </Suspense> ); } function MSWProviderWrapper({ children, }: Readonly<{ children: React.ReactNode; }>) { // use 사용해서 promise 실행 기다리고 children return use(mockingEnabledPromise); return children; } 제 추측으로는 MSWProvider에 있는 Suspense때문에 ssr 이 안되는 것 같은데, 맞는지 궁금합니다.그리고 맞다면 next 사용시 msw로 데이터를 mocking 하게 되면 ssr 확인을 어떻게 해야 할까요??
-
미해결Next + React Query로 SNS 서비스 만들기
getPostRecommend() tags 속성 사용
getPostRecommend() 함수 내부에서tags : ['post', 'recommends']로 설정이 되어있는데, queryClient.prefetch 함수나, useQuery 함수의 queryKey와 항상 동일하게 일치시켜야 하나요? 불러온 데이터를 캐싱할 경우, react-query에서만 관리를 키를 관리해도 되지 않을까요?
-
미해결Next + React Query로 SNS 서비스 만들기
ISR로 블로그 구현시 궁금증 질문있습니다!!
안녕하세요!좋은 강의 감사합니다이번 강의를 수강하며 개념적으로 궁금한 점이 있어서 질문드려봅니다. ISR로 블로그 글을 revalidate 시간을 24시간으로 많이 넣어둔다고 하셨습니다. 티스토리나 벨로그에선 기존 글을 수정하면 바로 업데이트가 됩니다. 제가 아는 지식으로 ISR을 하게 된다면 24시간 동안 수정해도 기존 데이터를 보여주어야 한다고 생각해서 ISR보단 SSR이 적합하다고 생각했습니다. ISR은 이러한 문제를 해결할 수 있는지 궁금합니다.블로그 글이나 뉴스같은 경우 대량의 데이터이기 때문에 그만큼 대량의 페이지가 HTML 파일로 생성되어 큰 용량을 차지할 것 같다는 생각이 들었습니다.이러한 부분은 어떻게 해결할 수 있는지 궁금합니다.ISR / SSG를 사용하는 페이지 경로로 Next/Link를 사용하여 접속할 경우 클라이언트 사이드 라우팅이 이루어진다고 알고 있습니다. 이 경우 미리 빌드된 HTML 파일을 렌더링하는 ISR/SSG의 장점이 사라지는 것처럼 보이는데, Next/Link에서 ISR/SSG를 사용할 때 어떤 이점이 있는지 궁금합니다.소중한 시간내어 읽어주셔서 감사합니다.
-
해결됨Next + React Query로 SNS 서비스 만들기
Error: connect ECONNREFUSED ::1:6379 트러블 슈팅
문제 발생: npm run start:dev 시 위와 같은 에러 발생Error: connect ECONNREFUSED ::1:6379 at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1615:16) { errno: -4078, code: 'ECONNREFUSED', syscall: 'connect', address: '::1', port: 6379 } 문제 원인:Redis 연결 실패 → 애플리케이션이 Redis 서버(포트 6379)에 연결하려 했지만 거부됨.ECONNREFUSED 오류는 Redis가 실행되지 않거나, 접근할 수 없는 상태일 때 발생. 문제 해결:Redis 기본 설정은 IPv6 (::1)을 사용함.IPv4(127.0.0.1)로 강제 변경해 해결localhost가 아닌 127.0.0.1로 변경// .env 기존 코드 REDIS_URL=redis://localhost:6379 // .env 변경 코드 REDIS_URL=redis://127.0.0.1:6379 위 에러 만나고 애쓴 부분 공유합니다. 혹시 잘못된 해결 방법이라면 알려주시면 감사하겠습니다!
-
미해결Next + React Query로 SNS 서비스 만들기
Parallel Routes시 default.tsx 와 page.tsx의 차이가 궁금합니다.
해당 강좌 default.tsx에서 기본 페이지로 사용시킬때 default.tsx를 처리해두고 빈페이지를 구성하는 형태를 사용하는 예제를 따라서 진행해보았는데요. 여기서 궁금한점이 page.tsx를 @modal에 하나더 유지시키고 return null 처리를 하는 컴포넌트를 두어도 똑같이 동작은 되는데, default.tsx를 좀 더 권장하는 이유가 있을까요?
-
해결됨Next + React Query로 SNS 서비스 만들기
요청 메모화와 데이터 캐시에 대해 제가 이해한 것이 맞는지 궁금합니다
아래가 제가 이해한 내용인데 맞을까요?요청 메모화ssr 요청 사이클 내에서만 페치 결과를 캐싱하여 중복 요청 방지 (한 서버 요청 사이클 끝나면 초기화)예를들어 Home컴포넌트와 자식 컴포넌트인 Header 컴포넌트에서 렌더링 시에 동일한 요청을 날렸다면 Home컴포넌트의 요청이 캐싱되고 Header 컴포넌트의 요청은 캐싱된 값 사용데이터 캐시한 ssr 요청 사이클 내 뿐만 아니라 서버에 전역적으로 유지되는 캐시요청 메모화와 다르게 서버 요청 끝나도 초기화되지 않고 지속됨즉, 여러 클라이언트 요청 간에도 유지됨A가 먼저 페이지 요청 → 백엔드에 요청 후 넥스트 서버에 데이터 캐싱B가 같은 페이지 요청 → 백엔드 요청 없이 넥스트 서버 캐시에서 데이터 반환결론즉, 요청 메모화는 렌더링 최적화, 데이터 캐시는 네트워크 요청 절약을 위한 것
-
미해결Next + React Query로 SNS 서비스 만들기
백엔드 api가 따로 있을 경우의 fetch 요청
Next.js에서 서버 컴포넌트는 서버에서 실행되기 때문에, useEffect 없이 컴포넌트 내부에서 직접 fetch 요청을 보낼 수 있는 것으로 알고 있습니다.또한, 서버 액션은 백엔드 없이 Next.js 풀스택 환경에서 DB에 직접 접근할 때 주로 사용하는 것으로 이해하고 있습니다.그렇다면, 별도로 백엔드가 존재하는 경우에도 서버 액션을 사용할 필요가 있나요? 그리고 실제로 현업에서도 백엔드가 따로 있을 경우에 서버액션을 사용하나요?감사합니다!
-
미해결Next + React Query로 SNS 서비스 만들기
useEffect의 의존성 배열에 fetchNextPage를 넣는 이유가 궁금합니다
useEffect(() => { if (inView) { hasNextPage &&fetchNextPage(); } }, [inView, hasNextPage, fetchNextPage]);이렇게 useEffect를 작성하셨는데 fetchNextPage를 useEffect의 의존성배열에 넣는 이유가 궁금합니다
-
미해결Next + React Query로 SNS 서비스 만들기
레이아웃은 서버 데이터에 변경사항이 있을 때는 렌더링을 해주나요?
레이아웃은 네비게이션을 해도 상태를 유지하고 렌더링을 하지 않는다라고 이해했습니다.그런데, 오른쪽 레이아웃을 만들면서 패러렐 라우트라던지 template를 쓰지 않고 (비록 컴포넌트를 분리하긴 했으나) 서버에서 데이터를 뿌려주는 미래를 상상하고 쭉 코드를 짜고 계시는 모습을 보면서,레이아웃이 서버 데이터는 변경사항을 화면에 반영해주는 것처럼 이해가 되었습니다.뒷부분은 수강하지 않았습니다. 제가 잘못 이해 중인가요?
-
미해결Next + React Query로 SNS 서비스 만들기
서버, 클라이언트 데이터 중복 호출 문제
강의와 같이 구현한다면page.tsx 에서 데이터 fetchPostRecommends.tsx 에서 는 다시 데이터를 fetch 하지 않고 HydrationBoundary를 통해 넘어온 데이터를 사용이렇게 page.tsx 에서만 데이터가 fetch 되어야 맞는거지요 ?제가 구현한 소스코드에서는 getPostRecommends 쪽에 로그를 찍어서 확인해보니 page.tsx 에서도 한번 호출하고 PostRecommends.tsx 에서도 한번 호출해서 총 2번 로그가 찍히더라구요.이론대로라면 page.tsx 에서 1번 호출했을 때만 로그가 찍혀야 하는 것이 맞는거죠?아 강의는 " 클라이언트 react-query" 입니다.
-
미해결Next + React Query로 SNS 서비스 만들기
router.replace를 하는데 setCurrent에서 변경한 상태가 유지?
라우터가 이동하면 상태가 초기화 됐던걸로 아는데, setCurrent는 왜 계속 유지가 되나요? 쉘로우 라우팅도 아닌데,,, 제가 잘못알고있나요?
-
미해결Next + React Query로 SNS 서비스 만들기
next-auth 사용 이유
안녕하세요. 백엔드에서 connect.sid 쿠키와 next-auth의 auth.js-session-token을 사용해서 유저 인증을 하는 것 처럼 보입니다. 프론트서버에서 유저가 접근 시 세션 쿠키(connect.sid)를 받아 백엔드에 유저 세션을 확인한다면 next-auth를 사용하지 않아도 되는 것처럼 보입니다. 백엔드에서 auth.js-sessoin-token으로 유저 인증을 한다면 역시 세션 쿠키(connect.sid)가 필요없는 것처럼 보입니다.connect.sid와 auth.js-session-token 쿠키를 모두 사용해야 하는 이유가 있을까요? 만약 둘 다 사용하는게 좋다고 한다면 동기화가 필요할 것 같은데(만료 시간, 세션 무효화 등..), 이건 어디서 어떻게 처리하는게 좋을까요?(예외 질문) connect.sid의 쿠키에 httpOnly 옵션이 false여도 괜찮은가요? 혹시 백엔드에서 connect.sid의 쿠키를 httpOnly로 보냈다면 쿠키파싱이 제대로 안된 것 같습니다. 아래 모듈로 쿠키파싱하면 되는 것 같습니다. 혹시나하고 남겨봅니다. //현재 코드 //import cookie from "cookie"; //const parsed = cookie.parse(setCookie); import { parseSetCookie } from "next/dist/compiled/@edge-runtime/cookies"; const parsed = parseSetCookie(setCookie);
-
미해결Next + React Query로 SNS 서비스 만들기
cache: "force-cache" 질문이 있습니다.
const res = await fetch( `${process.env.NEXT_PUBLIC_BASE_URL}/api/posts/recommends?cursor=${pageParam}`, { next: { tags: ["posts", "recommends"], }, //cache: "force-cache", } );'/home'에서 포스팅 로딩 시또는 새로고침 시에 데이터를 최초 조회하는 시점에서cache: "force-cache"를 제거하면 포스팅이 노출되고포함하면 포스팅이 안 나오는 현상이 있는데어떤 이유인지 의심 가는 부분을 못 찾겠어서 질문 드립니다. cache: "force-cache" 주석처리데이터 존재 cache: "force-cache"빈 배열
-
미해결Next + React Query로 SNS 서비스 만들기
queryKey 질문
혹시 useQuery에 작성하는 queryKey와 queryFn함수에 작상하는 next.tags의 queryKey는 무슨 상관관계가 있는 건가요 ? 둘의 값이 무조건 같아야 하는 줄 알았는데 useQuery엔 searchParams / queryFn함수엔 searchParams.q로 작성하셔서 여쭤봅니다. (next.tags엔 문자열만 가능한 점은 알고있습니다 ! )
-
미해결Next + React Query로 SNS 서비스 만들기
Nextjs 에서의 컴포넌트 작성법
강의 진행 도중 생긴 에러는 아니고, 순수한 궁금증에 질문 남깁니다.제가 React 로 프론트엔드 개발은 항상 모든 컴포넌트를 화살표 함수로 작성했었습니다. 이를테면const Home = () => {}; export default Home; 이런식으로 작성하였는데요, 근데 상기 Nextjs 강의에서도 그렇고 다른 수많은 블로그 글들에서는 전통적인 함수 선언식을 사용하더라구요. export default function Home() {}; 그 이유가 무엇일까요? 모두가 사용하는데는 이유가 있는거 같은데 관련 자료를 찾을 수가 없어서 질문드립니다. 혹시나 강제된 문법인가 싶어 해당 강의에서 사용된 페이지들을 화살표함수로 바꿔서 작성해봤는데 똑같이 에러 없이 잘 실행이 되더군요..