묻고 답해요
164만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지
린캔버스 프로젝트 공부중에 z인덱스 값.
선생님 린캔버스 프로젝트를 따라서 공부하고 있는데요.해더부분에 햄버거 버튼을 클릭하면 나오는 aside 메뉴가 z-9999를 줬는데요홈에 있는 돋보기가 더 위에서 노출되서요. 홈에 있는 서치 아이콘은 z-0으로 줬거든요 제가 뭘 잘못하고 있을까요 ㅠ
-
해결됨[풀스택 완성] Supabase로 웹사이트 3개 클론하기 (Next.js 14)
smtp부분
✅ 모든 질문들은 슬랙 채널에서 답변드리고 있습니다.💡 ”로펀의 인프런 상담소” 슬랙 채널 가입하기 💡평일중에는 퇴근 이후(저녁 7시)에 답변을 받아보실 수 있고, 주말중에는 상시 답변드리고 있습니다. 안녕하세요, smtp 부분 이메일 인증 부분 진행하고있는데,smtp를 설정해야하는건가요? 그렇다면 어떻게 설정해야할까요?리센드를 이용하면 도메인이 무조건 필요한거같아서요
-
해결됨아바타 커뮤니티앱 만들기 (React Native Expo)
console.log를 해도 터미널에 표시가 되지 않아요
console.log 를 해도 vscode 터미널상에서 로그가 뜨지 않는데,찾아보니 최신 ReactNative버전에선 React Native DevTools를 사용하라는 말이 있더라구요. 혹시 React Native DevTools에서 console.log를 확인하는 방법이 있을까요? 단순히 j눌러서 열고 "회원가입하기" 버튼 눌러도 로그가 안나오네요...ㅠㅠ // package.json { "name": "community", "main": "expo-router/entry", "version": "1.0.0", "scripts": { "start": "expo start", "reset-project": "node ./scripts/reset-project.js", "android": "expo start --android", "ios": "expo start --ios", "web": "expo start --web", "test": "jest --watchAll", "lint": "expo lint" }, "jest": { "preset": "jest-expo" }, "dependencies": { "@expo/vector-icons": "^14.0.2", "@react-navigation/bottom-tabs": "^7.2.0", "@react-navigation/native": "^7.0.14", "expo": "~52.0.37", "expo-blur": "~14.0.3", "expo-constants": "~17.0.7", "expo-font": "~13.0.4", "expo-haptics": "~14.0.1", "expo-linking": "~7.0.5", "expo-router": "~4.0.17", "expo-splash-screen": "~0.29.22", "expo-status-bar": "~2.0.1", "expo-symbols": "~0.2.2", "expo-system-ui": "~4.0.8", "expo-web-browser": "~14.0.2", "react": "18.3.1", "react-dom": "18.3.1", "react-native": "0.76.7", "react-native-gesture-handler": "~2.20.2", "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "4.12.0", "react-native-screens": "~4.4.0", "react-native-web": "~0.19.13", "react-native-webview": "13.12.5" }, "devDependencies": { "@babel/core": "^7.25.2", "@types/jest": "^29.5.12", "@types/react": "~18.3.12", "@types/react-test-renderer": "^18.3.0", "jest": "^29.2.1", "jest-expo": "~52.0.4", "react-test-renderer": "18.3.1", "typescript": "^5.3.3" }, "private": true }
-
미해결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 입력 가지고 무언가 활성화 되는 것 자체가 조금 이해가 되지 않아서... 일단 재랜더링이라고 표현으로 남겨두었습니다...
-
미해결React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지
삭제기능 구현부분에 대해 질문있습니다.
안녕하세요 삭제기능 구현 강의를 보다가 조금 어려운 부분이 있어 질문을 드립니다. canvas.js 파일에서 getCanvases함수와 createCanvas함수는 canvases.get, canvases.post앞에 return이 붙었고 이건 다른 곳에서 사용할 수 있다라고 알고 있습니다. 그럼 deleteCanvas는 왜 똑같이 return을 사용하지 않고 async await을 사용했는지 궁금하고 왜 위에 두 함수는 return을 사용했는지도 궁금합니다.
-
미해결React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지
캔버스 프로젝트에서 파일명에 대해 질문있습니다.
안녕하세요 현재 캔버스 프로젝트 부분을 공부중입니다. 공부를 하면서 파일에 대해 궁금한점이 생겼는데 컴포넌트에는 뒤에 .jsx를 작성했는데 api폴더에서 canvas와 http 파일에는 .js가 붙은 이유가 뭔지 궁금합니다.
-
해결됨React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지
함수 정의 위치
안녕하세요! React로 프로젝트를 진행할 때 여러 함수들(이벤트 핸들러나 로직 처리 함수 등)을 구현하게 되는데,이런 함수들을 어느 위치에서 정의하고 관리하는 것이 좋은지 궁금합니다.예를 들어, B 컴포넌트 안에 A 컴포넌트가 있고, A에서 어떤 이벤트 함수가 필요하다면그 함수를 B에서 정의하고 props로 A에 전달하는 식으로 처리하는 것이 React에서 일반적인 방식인가요?그리고 만약 그 함수가 여러 컴포넌트에서 재사용되어야 한다면,이들 컴포넌트의 공통 상위 컴포넌트까지 올라가서 정의한 후 각 컴포넌트에 전달하는 방식이 맞는지도 궁금합니다. 혹시 더 좋은 구조나 패턴이 있다면 조언 주시면 감사하겠습니다!
-
해결됨[풀스택 완성] Supabase로 웹사이트 3개 클론하기 (Next.js 14)
모듈 에러
✅ 모든 질문들은 슬랙 채널에서 답변드리고 있습니다.💡 ”로펀의 인프런 상담소” 슬랙 채널 가입하기 💡평일중에는 퇴근 이후(저녁 7시)에 답변을 받아보실 수 있고, 주말중에는 상시 답변드리고 있습니다. 모듈 에러가 뜨는데 수파베이스 설치를 잘못 한 것일까요? 검색해서 이리 저리 해보아도 잘 해결이 되지 않네요. 도와주세요~
-
미해결React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지
vs code 검색안됨
터미널에서 코드 입력해서 vs코드 열 수 있도록 하는 설정 강의보고 따라했는데"code" 쳐도 제 vs목록에 PATH에 'code'명령 설치 라는 항목이 없어요ㅠㅠ
-
해결됨[리뉴얼] 맛집 지도앱 만들기 (React Native & NestJS)
컴포넌트 자동 import 관련
안드로이드 윈도우로 개발중에 있습니다 강의를 보면 <Button/> 컴포넌트 <Text/> 컴포넌트 추가하면 자동으로 import 부분에 추가가 되던데 전 안되서.. 혹시 어떤 설정해야하나요? 아님 어떤 Extension 설치해야하나요?? /** * Sample React Native App * https://github.com/facebook/react-native * * @format */ import React from 'react'; import {Button, StyleSheet, Text, View} from 'react-native'; function App(): React.JSX.Element { return ( <View style={styles.conatiner}> <Text>텍스트</Text> <Button title="버튼이름" /> </View> ); } const styles = StyleSheet.create({ conatiner: { backgroundColor: 'red', }, }); export default App;
-
해결됨[리뉴얼] 맛집 지도앱 만들기 (React Native & NestJS)
lecture/섹션-수업코드 github
안녕하세요 lecture/섹션-수업코드 github 404 뜨더라구요ㅜ
-
해결됨아바타 커뮤니티앱 만들기 (React Native Expo)
react-hook-form handleSubmit이 동작하지 않습니다
❗질문 작성시 꼭 참고해주세요에러 메세지에서 단서를 찾을 수 있는 경우가 많습니다. 에러 메세지를 읽고 한번 검색해보시는것을 추천드립니다.질문글을 작성하실때는, 현재 문제(또는 에러)와 코드나 github을 첨부해주세요.개발중인 OS, ReactNative, Node 버전 등의 개발환경을 알려주셔야합니다.에러메세지는 일부분이 아닌 전체 상황을 올려주세요. 일부만 보여주시면 답변이 어렵습니다.에러 잘라서 올리시면 안됩니다!(에러 일부만 자르거나 일부만 복사하지말아주세요) function EmailInput() { const { control } = useFormContext() return ( <Controller name="email" control={control} render={({ field: { onChange, value } }) => ( <InputField label="이메일" value={value} onChangeText={onChange} placeholder="이메일을 입력해주세요." /> )} /> ) } // signup 페이지 마크업 부분입니다 const signupForm = useForm<FormValues>({ defaultValues: { email: '', password: '', passwordConfirm: '' } }) async function onSubmit(data: FormValues) { console.log(data) } return ( <FormProvider {...signupForm}> <View style={styles.container}> <EmailInput /> <PasswordInput /> <PasswordConfirmInput /> </View> <FixedBottomCTA label="회원가입하기" onPress={() => { console.log('onPress::::') signupForm.handleSubmit(onSubmit) }} /> </FormProvider> ) export default function FixedBottomCTA({ label, onPress }: FixedBottomCTAProps) { const inset = useSafeAreaInsets() return ( <View style={[styles.fixed, { paddingBottom: inset.bottom || 12 }]}> <CustomButton label={label} onPress={onPress} /> </View> ) } function CustomButton({ label, size = 'large', variant = 'filled', ...props }: CustomButtonProps) { return ( <CustomPressable style={({ pressed }) => [ styles.container, styles[size], styles[variant], pressed && styles.pressed ]} {...props} > <Text style={styles[variant]}>{label}</Text> </CustomPressable> ) } export const CustomPressable = ({ children, ...props }: PressableProps) => { const pressHandlerProps = Platform.OS === "android" ? { onPressIn: props.onPress || props.onPressIn } : { onPress: props.onPress }; // console.log('pressHandlerProps:::::', pressHandlerProps) return ( <Pressable {...props} {...pressHandlerProps}>{children}</Pressable> ) } node 20vhookform 7.55vios android 동일 증상react-native 0.76.9v 버전입니다.하위 버튼 컴포넌트들에서 onPress 함수만 별도 분리하여 console 찍어봐도 동작하지 않고 signupForm.handleSubmit(onSubmit) 윗부분 console 만 실행되고 있습니다차라리 터미널에 에러 코드라도 나타나면 좋은데 아예 아무런 반응이 없어서 해결에 애를 먹고 있습니다
-
미해결React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지
얕은복사와 깊은복사 질문
안녕하세요. 얕은복사와 깊은복사에 대해 질문합니다.제가 알기론 지금 예시를 든let person1 = {firstName:'짐', lastName:'코딩'};let person2 = {...person1};person1.firstName = 'GYM'console.log('person1: ', person1);console.log('person2: ', person2);이거를 깊은 복사라고 하셨는데얕은 복사 아닌가여?? 값이 원시값이라서 참조 공유 문제가 안생기는거 아닌가요??깊은 복사는 모든 깊이까지 값 자체를 완전하게 복사해야되는걸로 알고 있어서 cloneDeep이나 JSON.parse(JSON.stringify(person1)) 이런식으로 써야되는걸로 알고 있거든여.const items = [...todos] 도 얕은 복사지만 새로운 배열 주소를 만들어서 리액트에서는 변화가 생겼다고 판단하고 리렌더링이 되는걸로 알고 있는데 아닌가요??
-
해결됨[리뉴얼] 맛집 지도앱 만들기 (React Native & NestJS)
맵에서 onLongPress 가 동작 하지 않습니다.
안녕하세요.강의 내용을 따라 하다가갑자기 맵에서 onLongPress 가 동작 하지 않는데혹시 어떤 컴포넌트에서 long press 이벤트를 가지고 가는지?관련 디버깅 방법이 있을까요?
-
해결됨아바타 커뮤니티앱 만들기 (React Native Expo)
layout tab 설정 문의.
탭 설정 관련 문의 드립니다.현재 디렉토리는 아래 이미지와 같은 구조이고, 하단의 스크립트처럼 Tab 설정에는 Home / Profile / setting 3가지가 명시 된 상태인데 이전 미션에서 작성한 mission.tsx가 하나의 탭으로 잡히고 있습니다.RN 구조상 (tabs) 디렉토리 하단에 파일들이 자동으로 탭으로 잡히는 구조인지, 별도의 설정으로 뺄 수 있는지 알수 있을까요?? export default function TabLayout() { return ( <Tabs screenOptions={{ tabBarActiveTintColor: "black", headerShown: false, }} > <Tabs.Screen name="index" options={{ title: "Home", }} /> <Tabs.Screen name="my" options={{ title: "Profile", }} /> <Tabs.Screen name="setting" options={{ title: "setting", }} /> </Tabs> ); }
-
해결됨Next + React Query로 SNS 서비스 만들기
user 정보를 불러오는 useQuery에 타입을 지정해주는 이유가 궁금합니다.
export const getUser: QueryFunction<User, [_1: string, _2: string]>...생략getUser 함수에서 타입을 지정하면 해당 함수를 사용하는 useQuery에서는 자동으로 리턴 데이터 타입이 User로 나오던데 한번 더 useQuery에서 타입을 지정해주시는 이유가 궁금합니다!
-
미해결React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지
추가 기능과 삭제 기능에 대해 질문있습니다.
안녕하세요 강의 너무 잘 보고 있습니다. 공부를 하다가 궁금한점이 생겨서 질문을 드립니다.메모를 추가 삭제 기능에서 처음에는 추가같은 경우에는....const handleAddNote = () => { setNotes([...notes, {id: "", content: ""}]) }이런식으로 ...notes로 기존 값에다가 id, content로 새로운 메모를 추가하는 기능으로 알고 있는데 나중에는...// 새로운 캔버스 export function createCanvas() { const newCanvas = { title: uuid().substring(0, 4) + '새로운 린 캔버스', lastModified: dayjs().format('YYYY-MM-DD HH:mm:ss'), category: '신규', }; return canvases.post('/', newCanvas);이런식으로 추가기능을 만들고 삭제도 filter를 이용을 하는데 나중에는 export async function deleteCanvas() { await canvases.delete(`/${id}`); } 이런식으로 삭제 기능을 작성이 되었더라구요. 첫번째 코드들은 처음에는 추가, 삭제기능이 되지만 새로고침 후에는 다시 원래대로 나오고 두번째 코드들은 서버에서 추가, 삭제 기능을 만들어서 새로고침을 하면 실제로도 추가, 삭제 기능을 하게 되는 것인가요? 만약 그렇다면 첫번째 코드들은 새로고침을 하면 원대대로 되는데 왜 사용이 되는건지 궁금합니다.
-
미해결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 확인을 어떻게 해야 할까요??