묻고 답해요
164만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨(2025) MBTI 테스트 기반 수익형 웹사이트 만들기 - <코딩 배워 사업하자>
썸네일 리스트 페이지 만들기 함수 질문입니다.
function ThumbnailList() { const [testList] = useState(initialState: TESTS); return <div> {testList?.map(callbackfn: (test) => ( <img src={test?.info?.thumbImage} alt={test?.info?.mainUrl} key={test?.info?.mainUrl} />))} </div>; }useState 함수에 initialState:와 map 함수에 있는 callbackfn:이 있으면 문제가 생기는데 빼야 하는건가요? 아니면 문법이 버전때문에 다른건가요?
-
해결됨React, Node.js, MongoDB로 만드는 나만의 회사 웹사이트: 완벽 가이드
logout 기능에서 req.cookies.token == undefined가 되는 문제
비슷한 질문이 있어서 5-2강까지 강의를 들어서 adminLogin까지 구현을 하였습니다.앞의 4-6강에서 로그아웃을 시도하였을 때, 400 Bad Request : 이미 로그아웃된 상태로 나옵니다. 그래서 console.log(req.cookies.token)을 해보았을 때, undefined가 나옵니다.adminLogin을 진행하여 브라우저에 쿠키가 제대로 저장되었는지 확인해보았는데, localhost:5173에서도 localhost:3000에서도 cookie에 token값이 저장되어있었습니다.index.js에 cookie-parser 또한 존재하는 상태입니다.질문을 올리기 전에 여러가지를 시도해보았는데, router.post() 에서는 req.cookies.token의 값을 undefined로 가져오고, router.get()에서는 정상적인 토큰값을 반환했습니다.어떻게 해야 router.post() 에서도 req.cookies.token값을 가져올 수 있을까요?thunder client로 GET http://localhost:3000/api/auth/getCookie를 했을 때도 token은 undefined 값이 출력되었다가 브라우저에서 주소로 접근하니 token값이 정상적으로 출력되었습니다.아래의 사진은 thunder client로 get 방식과 post 방식으로 보냈을 때의 차이를 담은 사진입니다.브라우저에 cookie가 정상적으로 저장된 사진입니다.// index.js require("dotenv").config(); const express = require("express"); const mongoose = require("mongoose"); const cookieParser = require("cookie-parser"); const cors = require("cors"); const userRoutes = require("./routes/user"); const app = express(); const PORT = 3000; app.use(cors({ origin: "http://localhost:5173", credentials: true, })); app.use(express.json()) app.use(express.urlencoded({extended : true})) app.use(cookieParser()); app.use("/api/auth", userRoutes); app.get("/", (req, res) => { res.send("Hello world"); console.log("token: " + req.cookies.token); }); app.post("/cookie", (req, res) => { console.log(req.cookies.token) res.send("api/auth"); }) mongoose .connect(process.env.MONGO_URL) .then(() => console.log("MongoDB와 연결이 되었습니다.")) .catch((error) => console.log("MongoDB와 연결에 실패했습니다: ", error)); app.listen(PORT, () => { console.log("Server is running"); }); // user.js const express = require("express"); const router = express.Router(); const bcrypt = require("bcrypt"); const User = require("../models/User"); const axios = require("axios"); const jwt = require("jsonwebtoken"); // const cookieParser = require("cookie-parser"); // router.use(express.json()) // router.use(express.urlencoded({extended : true})) // router.use(cookieParser()); router.post("/signup", async (req, res) => { try { const { username, password } = req.body; const existingUser = await User.findOne({ username }); if (existingUser) { return res.status(400).json({ message: "이미 존재하는 사용자입니다." }); } const hashedPassword = await bcrypt.hash(password, 10); const user = new User({ username, password: hashedPassword, }); await user.save(); res.status(201).json({ message: "회원가입이 완료되었습니다." }); } catch (error) { res.status(500).json({ message: "서버 오류가 발생했습니다." }); console.log(error); } }); router.post("/login", async (req, res) => { try { const { username, password } = req.body; const user = await User.findOne({ username }).select("+password"); if (!user) { return res.status("401").json({ message: "사용자를 찾을 수 없습니다." }); } if (!user.isActive) { return res .status(401) .json({ message: "비활성화된 계정입니다. 관리자에게 문의하세요." }); } if (user.isLoggedIn) { return res .status(401) .json({ message: "이미 다른 기기에서 로그인되어 있습니다." }); } const isValidPassword = await bcrypt.compare(password, user.password); if (!isValidPassword) { user.failedLoginAttempts += 1; user.lastLoginAttempt = new Date(); if (user.failedLoginAttempts >= 5) { user.isActive = false; await user.save(); return res.status(401).json({ message: "비밀번호를 5회 이상 틀려 계정이 비활성화되었습니다.", }); } await user.save(); return res.status(401).json({ message: "비밀번호가 일치하지 않습니다.", remainingAttempts: 5 - user.failedLoginAttempts, }); } user.failedLoginAttempts = 0; user.lastLoginAttempt = new Date(); user.isLoggedIn = true; // try { // const response = await axios.get("https://api.ipify.org?format=json"); // const ipAddress = response.data.ip; // user.ipAddress = ipAddress; // } catch (error) { // console.log("IP 주소를 가져오던 중 오류 발생: ", error.message); // } await user.save(); console.log("로그인 성공"); const token = jwt.sign( { userId: user._id, username: user.username }, process.env.JWT_SECRET, { expiresIn: "24h" } ); console.log(token); res.cookie("token", token, { httpOnly: true, secure: "production", sameSite: "strict", maxAge: 24 * 60 * 60 * 1000, }); console.log("쿠키 설정", ); const userWithoutPassword = user.toObject(); delete userWithoutPassword.password; res.json({ user: userWithoutPassword }); console.log("json 전달 후 종료"); } catch (error) { console.log("서버 오류: ", error.message); res.status(500).json({ message: "서버 오류가 발생했습니다." }); } }); router.post("/logout", async (req, res) => { try { const token = req.cookies.token; console.log(token); if (!token) { return res.status(400).json({ message: "이미 로그아웃된 상태입니다." }); } try { const decoded = jwt.verify(token, process.env.JWT_SECRET); const user = await User.findById(decoded.userId); if (user) { user.isLoggedIn = false; await user.save(); } } catch (error) { console.log("토큰 검증 오류: ", error.message); } res.clearCookie("token", { httpOnly: true, secure: "production", sameSite: "strict", }); res.json({ message: "로그아웃되었습니다." }); } catch (error) { console.log("로그아웃 오류: ", error.message); res.status(500).json({ message: "서버 오류가 발생했습니다." }); } }); router.delete("/delete/:userId", async (req, res) => { try { const user = await User.findByIdAndDelete(req.params.userId); if (!user) { return res.status(404).json({ message: "사용자를 찾을 수 없습니다." }); } res.json({ message: "사용자가 성공적으로 삭제되었습니다." }); } catch (error) { res.status(500).json({ message: "서버 오류가 발생했습니다." }); } }); router.post("/verify-token", (req, res) => { const token = req.cookies.token; if (!token) { return res .status(400) .json({ isValid: false, message: "토큰이 없습니다." }); } try { const decoded = jwt.verify(token, process.env.JWT_SECRET); return res.status(200).json({ isValid: true, user: decoded }); } catch (error) { return res .status(401) .json({ isValid: false, message: "유효하지 않은 토큰입니다." }); } }); // router.get("/getCookie", async (req, res) => { // res.send(req.cookies.token) // console.log("getCookie's token : " , req.cookies.token) // }) // router.post("/postCookie", async (req, res) => { // res.send(req.cookies.token) // console.log("postCookie's token : ", req.cookies.token) // }) module.exports = router;
-
해결됨React, Node.js, MongoDB로 만드는 나만의 회사 웹사이트: 완벽 가이드
맥북을 사용중인데, 터미널이 다릅니다,,,,!
강의에서는 command prompt를 사용하셨는데, 맥북에서는 zsh와 bash 중 어떤 것을 사용해야 할까요?
-
미해결처음 만난 리액트(React)
Chapter6 질문 드립니다
학습을 잘 따라가는 중 아무 오류나 에러는 없는데 콘솔에는 에러가 나네요 ㅜㅜ분명 코드도 똑같은데 이유를 잘 모르겠어 코드와 오류 사진 캡처 드립니다!
-
미해결파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 (장고 4.2 기준)
선생님 학습 방법 질문이 있습니다.
파이썬 기초 문법 학습 후 현재 강의를 듣는 중인데 2번 세션 맛보기 반복 숙달 어느정도까지 해야 다음 챕터로 넘어가는 것이 맞나요? 강의 이름은 맛보기인데 중요한 것 들 같아서 반복 숙달을 어느정도 까지 하는게 좋을 지 궁금합니다.
-
해결됨한 입 크기로 잘라먹는 Next.js(v15)
상태관리
next는 자동적으로 캐시 저장기능이 있고 next에서도 DB 데이터가 필요한 페이지에서 직접 불러와서 사용하는 것을 권장한다 라고 말씀해주셨는데요 그러면 next는 보통 zustand, recoli, redux와 같은 상태관리 라이브러리를 사용하지 않는 것을 추천하시나요?아니면 사용하는 것을 추천하시나요??
-
해결됨[자바스크립트부터 리액트까지] 포기없는 React로 가는 길 [Full vers.]
member.self-ex 코드 업로드요청
깃허브 url에 member.self-ex 코드가 없습니다.확인 후 업로드 부탁드려요 그리고, useEffect 제대로 써보기: 반복 작업 제거와 효과적인 클린업 3:43 이부분부터 몇초간 화면이 안보입니다..
-
미해결비전공자를 위한 진짜 입문 올인원 개발 부트캠프
Windows에서의 업로드 후 홈화면 상품이미지 오류 해결방법
공익을 위해 첨부합니다... Windows의 경우 Mac과 다른 방식으로 경로가 설정되는 탓인지 상품 업로드 후 홈화면에서 상품이미지가 엑박으로 보이는 오류가 발생합니다. 그동안 질문&답변 게시판에 올라온 모든 질문 및 답변을 참고해 해결을 해보고자 하였는데요, 그 어느 답변을 참고해도 해결이 되지 않아 눈물을 머금고 10분짜리 강의에 몇시간동안 매달려있었습니다. 그렇게 알게된 방법은... 저의 경우(1) 상품 업로드 화면에서 사진을 첨부한 후 개발자 도구의 Network에서 첨부한 사진의 링크를 보면 http://localhost:8080/upload/(상품이름).jpg이런 식으로 뜹니다.(2) 업로드 버튼을 누른 후 홈 화면으로 이동(3) 개발자 도구의 Network에서 새롭게 업로드된 사진의 링크를 보면 http://localhost:3000/upload/(상품이름).jpg 이런 식으로 뜹니다. 이를 해결하기 위해, grab-market-web 폴더 (사용자에 따라 폴더 이름은 다를 수 있음) → src → main → index.js에서 product-card 의 product-img 부분을 확인합니다.<div> <img className="product-img" src={`${API_URL}/${product.imageUrl}`} ></img> </div>src 링크를 다음과 같이 변경합니다. 그럼 상품 업로드 화면에서의 이미지와 홈화면에서의 이미지가 localhost:8080로 동일해지기 때문에 상품 사진이 정상적으로 보입니다. 물론 이렇게 코드를 수정하고 나면 기존에 저장해놨던 상품들의 이미지에 엑박이 뜹니다. (images/products/__ 이런 식으로 폴더 내 이미지와 연결해둔 링크들이 http://localhost:8080/images/products/__ 처럼 변경되니 엑박이 뜨는 것으로 추정됩니다.)어차피 이제 사진을 서버에 직접 업로드하는 방법으로 진행될 예정이니 그냥 기존의 상품들은 삭제하시면 될 것 같습니다. (DB Browser → 데이터 탐색 → 기존 상품 레코드 선택 → 현재 레코드 삭제하기 → 변경사항 저장하기 이용하면 삭제 가능합니다.) 저와 동일한 이유로 엑박 뜨는게 아니라면... 저도 모르겠습니다. 방법을 찾으시면 공유해주세요. 파이팅! +)상품 상세페이지를 들어가면 다시 엑박이 뜹니다.<div id="image-box"> <img src={`${API_URL}/${product.imageUrl}`} /> {console.log(product.imageUrl)} </div>이때는 product 폴더의 index.js에 들어가여 image-box 부분을 다음과 같이 변경해주세요. 원리는 위와 동일합니다. 그러면 상세페이지에서도 정상적으로 작동합니다.
-
미해결Next + React Query로 SNS 서비스 만들기
useEffect의 의존성 배열에 fetchNextPage를 넣는 이유가 궁금합니다
useEffect(() => { if (inView) { hasNextPage &&fetchNextPage(); } }, [inView, hasNextPage, fetchNextPage]);이렇게 useEffect를 작성하셨는데 fetchNextPage를 useEffect의 의존성배열에 넣는 이유가 궁금합니다
-
해결됨(2025) MBTI 테스트 기반 수익형 웹사이트 만들기 - <코딩 배워 사업하자>
호스팅 업체를 결국 AWS 로 정해야 하는 이유가 궁금합니다.
강의를 잘 보고 있습니다.클라우드 플레어가 상당한 장점이 있는 호스팅 업체로 보이는데요, 그럼에도 불구하고 실무에서는 결국 AWS를 선택하는 이유를 아래의 3가지로 정리하면 될까요?커스터마이징 제한느린 성능벤더 종속성이 외에 클라우드플레어에서 AWS로 이전할 수 밖에 없었던 에피소드가 있으신지 여쭤보고 싶습니다.
-
해결됨(2025) MBTI 테스트 기반 수익형 웹사이트 만들기 - <코딩 배워 사업하자>
VSCode에 node 설치 mkdir, ls 결과 확인
Visual Studid Code powershel 터미널l에서 node 설치 확인을 하고 mkdir과 ls를 각각 입력하면 위와 같이 나오는데 맞는 것인지요? 강의 화면과는 다른 것 같아서요.
-
미해결웹 게임을 만들며 배우는 React
Cannot find package 'react-refesh' 이런 에러 뜨시는 분들 보세요.
한시간 헤매다 다행히 해결책 찾았습니다.기존 강좌와 @pmmmwh/react-refresh-webpack-plugin 해당 플러그인 버전 차이 때문에 발생한 에러 같습니다. webpack.config.js 파일을 아래와 같이 전체적으로 수정해주세요.const path = require('path'); const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); const webpack = require('webpack'); const isDevelopment = process.env.NODE_ENV !== 'production'; module.exports = { name: 'word-relay-dev', mode: 'development', devtool: 'eval', resolve: { extensions: ['.js', '.jsx'], }, entry: { app: './client', }, // 입력 mode: isDevelopment ? 'development' : 'production', module: { rules: [{ test: /\.jsx?$/, exclude: /node_modules/, use: [ { loader: require.resolve('babel-loader'), options: { presets: [ ['@babel/preset-env', { targets: { browsers: ['> 5% in KR'], }, debug: true, }], '@babel/preset-react', ], plugins: [ isDevelopment && require.resolve('@babel/plugin-proposal-class-properties'), isDevelopment && require.resolve('react-refresh/babel'), ].filter(Boolean), }, }, ], }], }, plugins: [ isDevelopment && new webpack.HotModuleReplacementPlugin(), isDevelopment && new ReactRefreshWebpackPlugin(), ].filter(Boolean), output: { path: path.join(__dirname, 'dist'), filename: 'app.js', publicPath: '/dist/', }, // 출력 devServer: { // webpack 4버전 이상(5버전 포함)도 아래처럼 수정 필요 devMiddleware: { publicPath: '/dist' }, static: { directory: path.resolve(__dirname) }, hot: true, }, }; 공식문서는 아래 링크를 참고하세요.https://www.npmjs.com/package/@pmmmwh/react-refresh-webpack-plugin/v/0.5.0-rc.4
-
해결됨한 입 크기로 잘라먹는 Next.js(v15)
page를 클라이언트 컴포넌트로 작성해도 rsc_payload가 생성되는 이유는 무엇인가요??
안녕하세요! 덕분에 nextjs에 대해 깊에 알아가고 있습니다.제목처럼 page 컴포넌트에 'use client'를 명시하고 빌드 해도 해당 페이지에 대한 rsc_payload가 생성되어서 .next 빌드 파일을 살펴 보았습니다.페이지를 클라이언트 컴포넌트로 작성하던 서버 컴포넌트로 작성하던 .next 빌드 파일 server, static/chunk/app 폴더에 모두 page.js 파일이 생성되는 것을 확인했습니다.1. server 폴더에 있는 page.js는 해당 페이지에 필요한 번들 파일에 대한 내용인 거고, static 폴더에 있는 page.js는 해당 페이지에 그려질 내용에 대한 파일인 건가요??2. 그래서 page컴포넌트를 클라이언트 컴포넌트로 만들어도 페이지에서 사용할 번들 파일의 주소를 알아야 하기 때문에 rsc_payload를 만들어서 내려주는 건가요??
-
미해결Next + React Query로 SNS 서비스 만들기
레이아웃은 서버 데이터에 변경사항이 있을 때는 렌더링을 해주나요?
레이아웃은 네비게이션을 해도 상태를 유지하고 렌더링을 하지 않는다라고 이해했습니다.그런데, 오른쪽 레이아웃을 만들면서 패러렐 라우트라던지 template를 쓰지 않고 (비록 컴포넌트를 분리하긴 했으나) 서버에서 데이터를 뿌려주는 미래를 상상하고 쭉 코드를 짜고 계시는 모습을 보면서,레이아웃이 서버 데이터는 변경사항을 화면에 반영해주는 것처럼 이해가 되었습니다.뒷부분은 수강하지 않았습니다. 제가 잘못 이해 중인가요?
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스
padding 과 box-sizing 질문
14분 부터 css 전체선택자 box-sizing 이 border-box 라서 padding 을 하게되면 a 태그의 크기가 줄어들어야 하는 것 아닌가요? 왜 padding을 통해 크기를 키운다고 말씀하시나요?
-
미해결웹 게임을 만들며 배우는 React
해당 에러 뜨는 분들 보세요. "Uncaught TypeError: ReactDom.createRoot is not a function"
package.json 파일 보시면 리액트 버전이 나오실텐데 저는 19 버전이라 위같은 에러가 뜨네요. 따라서, 기존 코드를 (리액트 17버전 이하에서 정상작동)..const ReactDom = require('react-dom'); ReactDom.render(<WordRelay />, document.querySelector('#root')); 아래처럼 변경해주셔야 합니다.import { createRoot } from 'react-dom/client'; createRoot(document.querySelector('#root')).render(<WordRelay />);
-
해결됨한 입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지
async&await에서 reject시 처리 방법이 궁금합니다.
채찍피티에게 물어보니 try/catch로 처리하라고 하는데 이게 최선일까요..?
-
미해결React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지
eslint.config.js (flat config): eslint.config.js 파일에서 eslint-config-prettier를 import한 후, 다른 설정을 재정의할 수 있도록 설정 배열의 마지막에 추가합니다.
eslint.config.js (flat config): eslint.config.js 파일에서 eslint-config-prettier를 import한 후, 다른 설정을 재정의할 수 있도록 설정 배열의 마지막에 추가합니다.에서 어떻게 import해야하는가요? import eslint-config-prettier from 'eslint-config-prettier';로 하면 오류가 납니다.
-
미해결React 완벽 마스터: 기초 개념부터 린캔버스 프로젝트까지
.prettierrc를 적용했는데 저장하니 ;이 왜 안생길까요?
.prettierrc를 적용했는데 저장하니 ;이 왜 안생길까요?
-
해결됨한 입 크기로 잘라먹는 Next.js(v15)
스트리밍으로 받은 <script />내부의 데이터가 SEO에 영향이 가는지 알고 싶어요
안녕하세요 !강의를 듣기전부터 궁금했던 내용인데 강의를 듣고나서도 답을 찾지 못해서 질문을 남깁니다..!개발자도구 > Network 탭 > 문서 > 응답이나 소스보기에 서버사이드에서 불러온 데이터가 HTML로 만들어져있으면 SEO에 영향을 주는 데이터라고 알고 있어요제가 혼자 테스트해보려고 만든 스트리밍에서는 <script /> 내부에만 서버 사이드에서 패치한 데이터가 들어가있고 HTML로 만들어지지는 않았더라고요( 아마 <script />에 있는 정보가 RSC Payload겠죠..? )그래서 이게 SEO에 영향이 가는 데이터일지 궁금해서 여기저기 찾다가 발견한 Next Playground에서는 HTML로도 만들어지고 <script />에도 들어가있더라고요최종적으로 궁금한것은 <script />에만 들어있는 스트리밍된 데이터도 SEO에 영향을 주는지가 궁금해요그리고 영향을 주든 안주든 이런 정보는 어디서 찾아봐야하는지 어떻게 검증하는지 알고 싶습니다..!( 참고로 Playground에서 패치하는 첫번째 리스트의 이름인 Fusce commodo porta로 확인해봤습니다! )