월 17,600원
5개월 할부 시다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
로컬호스트 접속 시 Module not found: Can't resolve 'child_process' 에러
안녕하세요! 갑자기 로컬호스트에 접속이 안 되어서 질문합니다 npm run dev 하면 컴파일까지는 성공하는데 그 다음에 localhost로 접속하면 다음과 같은 에러가 뜹니다error - ./node_modules/worker-farm/lib/fork.js:3:0Module not found: Can't resolve 'child_process'nullError from chokidar (C:\): Error: EBUSY: resource busy or locked, lstat 'C:\DumpStack.log.tmp'Could not find files for / in .next/build-manifest.json(중간중간에 콘솔로그 찍힌건 생략했습니다) 개발자도구 확인해보면 에러는 안 나옵니다구글링해서 package.json 수정하는 방법이랑 _document.js에 import 'classlist.js' 하는방법 시도해봤는데 오류가 해결되지 않았습니다.. 어떻게 해야할까요ㅠ
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
빌드 시간..
넥스트는 12버전 사용하고 있습니다.빌드 할 때 보통 시간이 20초 정도 걸리나요?새로고침하면 20초 정도 계속 걸려서 테스트하기가 참.. 힘드네용..ㅜ
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
우분투 빌드 시간이 너무 깁니다.
ubuntu@ip-172-31-45-72:~/TwitterClone-FS-/front$ npm run build > react-nodebird-front@1.0.0 build > cross-env ANALYZE=true NODE_ENV=production next build Linting and checking validity of types .. ⨯ ESLint: ESLint configuration in .eslintrc is invalid: - Unexpected top-level property "parseOptions". ✓ Linting and checking validity of types Disabled SWC as replacement for Babel because of custom Babel configuration ".babelrc" https://nextjs.org/docs/messages/swc-disabled Webpack Bundle Analyzer saved report to /home/ubuntu/TwitterClone-FS-/front/.next/analyze/nodejs.html No bundles were parsed. Analyzer will show only original module sizes from stats file. Webpack Bundle Analyzer saved report to /home/ubuntu/TwitterClone-FS-/front/.next/analyze/edge.html Using external babel configuration from /home/ubuntu/TwitterClone-FS-/front/.babelrc ⚠ It looks like there is a custom Babel configuration can be removed : ⚠ Next.js supports the following features natively: ⚠ - 'styled-components' can be enabled via 'compiler.styledComponents' in 'next.config.js' ⚠ For more details configuration options, please refer https://nextjs.org/docs/architecture/nextjs-compiler#supported-features Creating an optimized production build ...계속 빌드를 진행하고있는데, ⨯ ESLint: ESLint configuration in .eslintrc is invalid: - Unexpected top-level property "parseOptions". 이부분, 통과못해도 계속해서 빌드가 진행되나요!? 아니면, 이부분때문에 지금 계속 빌드를 못하고 있는걸까요?평균적인 빌드시간을 알고 싶습니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
ubuntu front 빌드 에러
ubuntu@ip-172-31-45-72:~/TwitterClone-FS-/front$ npm install npm WARN EBADENGINE Unsupported engine { npm WARN EBADENGINE package: '@faker-js/faker@8.3.1', npm WARN EBADENGINE required: { node: '^14.17.0 || ^16.13.0 || >=18.0.0', npm: '>=6.14.13' }, npm WARN EBADENGINE current: { node: 'v12.22.9', npm: '8.5.1' } npm WARN EBADENGINE } npm WARN EBADENGINE Unsupported engine { npm WARN EBADENGINE package: 'next@13.5.6', npm WARN EBADENGINE required: { node: '>=16.14.0' }, npm WARN EBADENGINE current: { node: 'v12.22.9', npm: '8.5.1' } npm WARN EBADENGINE } npm WARN EBADENGINE Unsupported engine { npm WARN EBADENGINE package: 'styled-components@6.1.1', npm WARN EBADENGINE required: { node: '>= 16' }, npm WARN EBADENGINE current: { node: 'v12.22.9', npm: '8.5.1' } npm WARN EBADENGINE } up to date, audited 421 packages in 3s 105 packages are looking for funding run `npm fund` for details 1 high severity vulnerability To address all issues (including breaking changes), run: npm audit fix --force Run `npm audit` for details. ubuntu@ip-172-31-45-72:~/TwitterClone-FS-/front$ npm run dev > react-nodebird-front@1.0.0 dev > next /home/ubuntu/TwitterClone-FS-/front/node_modules/next/dist/lib/picocolors.js:134 const { env, stdout } = ((_globalThis = globalThis) == null ? void 0 : _globalThis.process) ?? {}; ^ SyntaxError: Unexpected token '?' at wrapSafe (internal/modules/cjs/loader.js:915:16) at Module._compile (internal/modules/cjs/loader.js:963:27) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10) at Module.load (internal/modules/cjs/loader.js:863:32) at Function.Module._load (internal/modules/cjs/loader.js:708:14) at Module.require (internal/modules/cjs/loader.js:887:19) at Module.mod.require (/home/ubuntu/TwitterClone-FS-/front/node_modules/next/dist/server/require-hook.js:64:28) at require (internal/modules/cjs/helpers.js:74:18) at Object.<anonymous> (/home/ubuntu/TwitterClone-FS-/front/node_modules/next/dist/build/output/log.js:55:21) at Module._compile (internal/modules/cjs/loader.js:999:30) ubuntu@ip-172-31-45-72:~/TwitterClone-FS-/front$ ls components next.config.js package-lock.json pages sagas utils hooks node_modules package.json reducers store ubuntu@ip-172-31-45-72:~/TwitterClone-FS-/front$ 이런 에러가 발생됩니다. unexpected token '?' 어떤 이유로 에러가 발생하고, 어떤식으로 해결해야지 실행할 수 있는지 알 고 싶습니다!!
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
next-auth 로그인 시 unauthorized 문제
문제가 몇일째 안풀리는게 있어 문의드려요제가 next-auth를 사용해서 로그인 프로세스를 해보고 있습니다.로컬에서 별도로 배포환경 만들어서 테스트를 했을 때에는 잘 되는데 배포시에 계속 Unauthorized 에러가 발생해서요제가 작성한 [...nextauth].ts 코드입니다.import NextAuth, { User } from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; import { refreshAccessToken } from 'utils/tokenRefresh'; export default NextAuth({ providers: [ CredentialsProvider({ name: 'Credentials', credentials: { userId: { label: 'UserId', type: 'text', placeholder: 'jsmith' }, password: { label: 'Password', type: 'password' }, }, authorize: async (credentials) => { const res = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/v1/users/signin`, { method: 'POST', body: JSON.stringify({ userId: credentials!.userId, password: credentials!.password, }), headers: { 'Content-Type': 'application/json' }, }); const user = await res.json(); // 로그인 성공 시 사용자 객체를 반환하고, 실패 시 null을 반환 if (res.ok && user) { console.log('ok user', user); return user; } else { console.log('error user', user); return false; } }, }), ], secret: process.env.NEXTAUTH_SECRET, // session: { // strategy: 'jwt', // maxAge: 0, // 브라우저가 닫히면 세션 종료 // // updateAge: 24 * 60 * 60, // 24시간마다 세션 갱신 (옵션) // }, callbacks: { async jwt({ token, user }) { // 사용자 로그인 시 토큰 설정 if (user) { return { ...token, accessJwt: user.result?.accessJwt, refreshJwt: user.result?.refreshJwt, companyId: user.result?.companyId, userName: user.result?.userName, accessTokenExpires: Date.now() + 3600 * 1000, }; } // 토큰 만료 확인 및 리프레시 if (Date.now() > token.accessTokenExpires!) { const newAccessJwt = await refreshAccessToken(token.refreshJwt!); return refreshAccessToken(newAccessJwt); } return token; }, async session({ session, token }) { if (token && token.accessJwt) { const customUser = session.user as User; if (!customUser.result) { customUser.result = { accessJwt: '', refreshJwt: '', companyId: '', userName: '', }; } customUser.result.accessJwt = token.accessJwt; customUser.result.refreshJwt = token.refreshJwt; customUser.result.companyId = token.companyId; customUser.result.userName = token.userName; session.user = customUser; } return session; }, }, }); NEXT_PUBLIC_BACKEND_URL 환경변수는 별도의 백엔드를 구성한 주소이구요 배포는 도커를 사용했습니다.FROM node:20.10 as builder # pnpm 설치 RUN npm install -g pnpm WORKDIR /usr/src/app COPY package*.json ./ RUN pnpm install ARG NEXT_PUBLIC_BACKEND_URL ARG NEXTAUTH_SECRET COPY . . RUN NEXT_PUBLIC_BACKEND_URL=https://${NEXT_PUBLIC_BACKEND_URL} NEXTAUTH_SECRET=${NEXTAUTH_SECRET} pnpm run build FROM node:20.10 RUN npm install -g pnpm WORKDIR /usr/src/app COPY --from=builder /usr/src/app/package*.json ./ RUN pnpm install --prod COPY --from=builder /usr/src/app . # COPY --from=builder /usr/src/app/.next ./.next EXPOSE 3000 CMD ["pnpm", "run", "dev"]NEXTAUTH_SECRET은 프론트 주소를 도커 실행 시 넣어줘야 한대서 도커 실행 할 때 환경변수로 따로 넣어줬구요Docs, Stackoverflow, ChatGPT 등 여러 방면으로 찾아봤는데 해결이 안되더라구요..강의하고는 별개 내용이긴 한데 더이상 물어볼 데가 없어서 강사님께 여쭈어봅니다..
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
Redux-Saga Login_REQUEST 문제입니다.
redux-saga 쪼개고 reducer 연결 하려고 하니 user reducer만 반응하고 LOG_IN_SUCCESS 는 반응을 하지 않습니다. 커뮤니티 게시판에서 여러가지를 확인해보고 해도 어디 부분이 잘못 된지 몰라 올려봅니다.. 제가 작성한 코드는 이러합니다. store/configureStore.js import { applyMiddleware, createStore, compose } from "redux"; import createSagaMiddleware from "redux-saga"; import { createWrapper } from "next-redux-wrapper"; import { composeWithDevTools } from "redux-devtools-extension"; import reducer from "../reducers"; import rootSaga from "../sagas"; const configureStore = (context) => { console.log("context", context); const sagaMiddleware = createSagaMiddleware(); const middlewares = [sagaMiddleware]; const enhancer = process.env.NODE_ENV === "production" ? compose(applyMiddleware(...middlewares)) // 배포용 : composeWithDevTools(applyMiddleware(...middlewares)); const store = createStore(reducer, enhancer); store.sagaTask = sagaMiddleware.run(rootSaga); return store; }; const wrapper = createWrapper(configureStore, { debug: process.env.NODE_ENV === "development", }); export default wrapper;reducers/index.js import { HYDRATE } from "next-redux-wrapper"; // HYDRATE = action import { combineReducers } from "redux"; import user from "./user"; import post from "./post"; const rootReducer = combineReducers({ index: (state = {}, action) => { switch (action.type) { case HYDRATE: console.log("HYDRATE", action); return { ...state, ...action.payload }; default: return state; } }, user, post, }); export default rootReducer; reducers/user.js export const initialState = { isLoggingIn: false, // 로그인 시도중 isLoggedIn: false, // 로그인 isLoggingOut: false, // 로그아웃 시도중 meUser: null, signUpData: {}, loginData: {}, }; export const LOG_IN_REQUEST = "LOG_IN_REQUEST"; export const LOG_IN_SUCCESS = "LOG_IN_SUCCESS"; export const LOG_IN_FAILURE = "LOG_IN_FAILURE"; export const LOG_OUT_REQUEST = "LOG_OUT_REQUEST"; export const LOG_OUT_SUCCESS = "LOG_OUT_SUCCESS"; export const LOG_OUT_FAILURE = "LOG_OUT_FAILURE"; export const loginRequestAction = (data) => ({ type: LOG_IN_REQUEST, value: data, }); export const logoutRequestAction = () => ({ type: LOG_OUT_REQUEST, }); const reducer = (state = initialState, action) => { // prettier-ignore switch(action.type) { case LOG_OUT_REQUEST : return {...state, isLoggingIn : true}; case LOG_IN_SUCCESS : return {...state, isLoggingIn : false, isLoggedIn:true, meUser:{...action.value, nickName:"Jay"}}; case LOG_IN_FAILURE : return {...state, isLoggingIn : false, isLoggedIn:false }; case "LOG_OUT_REQUEST" : return {...state, isLoggingOut:true}; case "LOG_OUT_SUCCESS" : return {...state, isLoggingOut:false, isLoggedIn:true, meUser:null}; case "LOG_OUT_FAILURE" : return {...state, isLoggingOut:false}; default: return state; } }; export default reducer;sagas/index.jsimport { all, fork, call } from "redux-saga/effects"; import userSaga from "./user"; export default function* rootSaga() { yield all([fork(userSaga)]); } sagas/user.js import { all, fork, put, delay, takeLatest } from "redux-saga/effects"; import { LOG_IN_FAILURE, LOG_IN_REQUEST, LOG_IN_SUCCESS, } from "../reducers/user"; function* logIn(action) { try { console.log("saga logIn"); // const result = yield call(logInAPI); yield delay(1000); yield put({ type: LOG_IN_SUCCESS, value: action.value, }); } catch (err) { console.error(err); yield put({ type: LOG_IN_FAILURE, error: err.response.data, }); } } function* watchLogIn() { yield takeLatest(LOG_IN_REQUEST, logIn); } function* watchLogOut() { yield takeLatest("LOG_OUT_REQUEST"); } export default function* userSaga() { yield all([fork(watchLogIn), fork(watchLogOut)]); }
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
23분 13초 관련 질문입니다.
리트윗 에러시, alert를 띄워주실 때, 게시글 . 수만큼 리렌더링이 발생하셔서 index.js 상위 페이지로 에러를 올려주셨는데, 설명하실떄 리트윗 에러에다가, 게시글 id까지 같이 넣어서, 그. 포스트 카드에만 에러메시지가 나오게 해서 해결하실 수있다고 하셨습니다. 어떤식으로 코드를 작성하면 되는지 해당 부분에 대한 코드 작성법도 알고싶습니다.어떤식으로 postId를 넘겨주고 useEffect를 활용할 수 있는지 알고 싶습니다.function retweetAPI(data) { return axios.post(`/post/${data}/retweet`); } function* retweet(action) { try { const result = yield call(retweetAPI, action.data); yield put({ type: RETWEET_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ type: RETWEET_FAILURE, error: err.response.data, }); } }
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
next 13,14버전으로 하고 있는데 antd에서 다음과같은 오류가 발생합니다.
SyntaxError: Cannot use import statement outside a module이란 에러가 발생하는데 왜그런걸까요 stackoverflow찾다보니 nextjs 바벨설정이 src 하위를 보게되있어서 es6문법을 변환못해준다고 next.config.js 파일에 아래 transpilePackages 설정을 저렇게 넣어주면 해당 오류가 사라지긴하는대 매번 이렇게 해야되는건지... 근본적인 해결하려면 어떻게 해야할까요const nextConfig = { reactStrictMode: true, transpilePackages: [ 'antd', '@ant-design', 'rc-util', 'kitchen-flow-editor', '@ant-design/pro-editor', 'zustand', 'leva', 'rc-pagination', 'rc-picker', 'rc-notification', 'rc-tooltip' ], }
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
Next 14 강의
안녕하세요,슬랙 채널에서 Next 14 강의 업로드 예정이시라는 글을 봤습니다. 해당 강좌의 리뉴얼 버전이라고 보면 될까요? 차이점이 궁금합니다.감사합니다.
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
미리보기가 보이지 않습니다
//app.js app.use('/', express.static(path.join(__dirname, 'uploads'))); //PostForm.js import { Button, Form, Input } from 'antd'; import React, { useCallback, useEffect, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; import useInput from '../hooks/useInput'; import { ADD_POST_REQUEST, REMOVE_IMAGE, UPLOAD_IMAGES_REQUEST, addPost, } from '../reducers/post'; const FormWrapper = styled(Form)` margin: 10px 0 20px; `; const ButtonStyle = styled(Button)` float: 'right'; `; const PostForm = () => { const { imagePaths, addPostLoading, addPostDone } = useSelector( (state) => state.post ); const dispatch = useDispatch(); const imageInput = useRef(); const [text, onChangeText, setText] = useInput(''); useEffect(() => { if (addPostDone) { setText(''); } }, [addPostDone]); const onSubmitForm = useCallback(() => { if (!text || !text.trim()) { return alert('게시글을 작성하세요.'); } const formData = new FormData(); imagePaths.forEach((p) => { formData.append('image', p); }); formData.append('content', text); return dispatch({ type: ADD_POST_REQUEST, data: formData, }); }, [text, imagePaths]); const onClickImageUpload = useCallback(() => { imageInput.current.click(); }, [imageInput.current]); const onChangeImages = useCallback((e) => { console.log('images', e.target.files); //배열모양을 띄는 객체 const imageFormData = new FormData(); [].forEach.call(e.target.files, (f) => { imageFormData.append('image', f); }); dispatch({ type: UPLOAD_IMAGES_REQUEST, data: imageFormData, }); }, []); const onRemoveImage = useCallback( (index) => () => { dispatch({ type: REMOVE_IMAGE, data: index, }); }, [] ); return ( <FormWrapper encType='multipart/form-data' onFinish={onSubmitForm}> <Input.TextArea value={text} onChange={onChangeText} maxLength={140} placeholder='어떤 신기한 일이 있었나요?' /> <div> <input onChange={onChangeImages} type='file' name='image' hidden multiple ref={imageInput} /> <Button onClick={onClickImageUpload}>이미지 업로드</Button> <ButtonStyle type='primary' htmlType='submit'> Twit </ButtonStyle> </div> <div> {imagePaths.map((item, i) => ( <div key={item} style={{ display: 'inline-block' }}> <img src={`http://localhost:3065/${item}`} style={{ width: '200px' }} alt={item} /> <div> <Button onClick={onRemoveImage(i)}>제거</Button> </div> </div> ))} </div> </FormWrapper> ); }; export default PostForm; //post.js const multer = require('multer'); const path = require('path'); const fs = require('fs'); const { Post, Image, Comment, User } = require('../models'); const { isLoggedIn } = require('./middlewares'); const router = express.Router(); try { fs.accessSync('uploads'); } catch (error) { console.log('uploads 폴더가 없으므로 생성합니다.'); fs.mkdirSync('uploads'); } //이미지나 동영상처리는 웬만하면 프론트에서 클라우드로 바로 올리는게 좋다. const upload = multer({ storage: multer.diskStorage({ destination(req, file, done) { done(null, 'uploads'); }, //배포때는 s3로 , 개발에는 드라이브에 filename(req, file, done) { const ext = path.extname(file.originalname); //확장자 추출 const basename = path.basename(file.originalname, ext); done(null, basename + '_' + new Date().getTime() + ext); //이나당151817842.png }, }), limits: { fileSize: 20 * 1024 * 1024 }, //20MB }); router.post('/', isLoggedIn, upload.none(), async (req, res, next) => { // POST /post try { const post = await Post.create({ content: req.body.content, UserId: req.user.id, }); if (req.body.image) { if (Array.isArray(req.body.image)) { const images = await Promise.all( req.body.image.map((image) => Image.create({ src: image })) ); await post.addImages(images); } else { const image = await Image.create({ src: req.body.image }); await post.addImages(image); } } console.log('POST', post); const fullPost = await Post.findOne({ where: post.id, include: [ { model: Image, }, { model: Comment, include: [{ model: User, attributes: ['id', 'nickname'] }], }, { model: User, //작성자 attributes: ['id', 'nickname'], }, { model: User, //좋아요 누른 사람 as: 'Likers', attributes: ['id'], }, ], }); res.status(201).json(fullPost); } catch (error) { console.error(error); next(error); } }); router.post( '/images', isLoggedIn, upload.array('image'), async (req, res, next) => { //POST /post/images try { console.log(req.files); res.json(req.files.map((v) => v.filename)); } catch (error) { console.error(error); next(error); } } ); 안녕하세요 제로초님! 질문이 있습니다.. 파일은 제대로 올라가서 uploads 폴더안에 있는데 화면에서 이미지를 가져오지 못하고있습니다.. 프론트-백 api통신도 잘 되는데, 왜 개발자도구-네트워크에서 이미지를 가져올 수 없는지 이유를 모르겠습니다 ㅠㅠ
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
[공유] Next.js 12버전 redirect
import { useRouter } from 'next/router'; const Profile = () => { ... const router = useRouter(); if (!me) { router.push('/'); return null; } ... }이런식으로 하면 잘 동작해요~
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
retweet 관련 질문이 있습니다.
다름이 아니라 리트윗빼고는 문제가 없습니다.하지만 리트윗을 하게 되면 데이터베이스에 userid가 null로 들어가면서 게시글을 불러올때 userid가 없기때문에 오류가 나는 것으로 보입니다... 해결해보려고 노력하는 중입니다만 어디가 문제인지 잘 모르겠습니다.const express = require("express"); const multer = require("multer"); const path = require("path"); const fs = require("fs"); const { Post, Image, Comment, User, Hashtag } = require("../models"); const { isLoggedIn } = require("./middlewares"); const router = express.Router(); try { fs.accessSync("uploads"); } catch (error) { console.log("uploads폴더가 없으므로 생성합니다."); fs.mkdirSync("uploads"); } const upload = multer({ storage: multer.diskStorage({ //어디에 저장할지 diskStorage => 하드디스크에 저장 destination(req, file, done) { done(null, "uploads"); }, filename(req, file, done) { // 제로초.png const ext = path.extname(file.originalname); // 확장자 추출(.png) const basename = path.basename(file.originalname, ext); // 제로초 done(null, basename + "_" + new Date().getTime() + ext); // 제로초15184712891.png }, }), limits: { fileSize: 20 * 1024 * 1024 }, // 20MB }); router.post("/", isLoggedIn, upload.none(), async (req, res, next) => { // 보기에는 "/"로 되어있지만 실제로는 "/post"로 되어있다. try { const hashtags = req.body.content.match(/#[^\s#]+/g); //hashtag 정규식 const post = await Post.create({ content: req.body.content, UserId: req.user.id, }); if (hashtags) { const result = await Promise.all( hashtags.map( (tag) => Hashtag.findOrCreate({ //있으면 가져오고 없으면 추가해라 where: { name: tag.slice(1).toLowerCase() }, }) //[[노드, true], [리액트, true]] 이런식으로 나옴 //slice는 글자만 떼기 위해 ex ) #react인 경우 react만 꺼냄 ) ); await post.addHashtags(result.map((v) => v[0])); //위에 같은 형식이기 때문에 0번째만 꺼내야함 } if (req.body.image) { if (Array.isArray(req.body.image)) { //여러개의 이미지를 올린 경우 const images = await Promise.all( req.body.image.map((image) => Image.create({ src: image })) ); await post.addImages(images); } else { const image = await Image.create({ src: req.body.image }); await post.addImages(image); } } const fullPost = await Post.findOne({ where: { id: post.id }, include: [ { model: Image, }, { model: Comment, include: [ { model: User, //댓글 작성자 attributes: ["id", "nickname"], }, ], }, { model: User, //게시글 작성자 attributes: ["id", "nickname"], }, { model: User, //좋아요 누른 사람 as: "Likers", attributes: ["id"], }, ], }); res.status(201).json(fullPost); //다시 프론트로 돌려주기 } catch (error) { console.error(error); next(error); } }); router.post("/:postId/comment", isLoggedIn, async (req, res, next) => { //:postId는 동적으로 바뀐다. //POST /post/comment // 보기에는 "/"로 되어있지만 실제로는 "/post"로 되어있다. try { const post = await Post.findOne({ //이 게시물이 진짜 있는지. where: { id: req.params.postId }, }); if (!post) { return res.status(403).send("존재하지 않는 게시글입니다."); } const comment = await Comment.create({ content: req.body.content, PostId: parseInt(req.params.postId, 10), //문자열로 넘어가기 때문에 int형으로 바꿔줘야한다. UserId: req.user.id, }); const fullComment = await Comment.findOne({ where: { id: comment.id }, include: [ { model: User, attributes: ["id", "nickname"], }, ], }); res.status(201).json(fullComment); } catch (error) { console.error(error); next(error); } }); router.patch("/:postId/like", isLoggedIn, async (req, res, next) => { //PATCH /post/1/like\ try { const post = await Post.findOne({ where: { id: req.params.postId } }); if (!post) { return res.status(403).send("게시글이 존재하지 않습니다."); } await post.addLikers(req.user.id); res.json({ PostId: post.id, UserId: req.user.id }); } catch (error) { console.error(error); next(error); } }); router.delete("/:postId/like", isLoggedIn, async (req, res, next) => { //DELETE /post/1/like try { const post = await Post.findOne({ where: { id: req.params.postId } }); if (!post) { return res.status(403).send("게시글이 존재하지 않습니다."); } await post.removeLikers(req.user.id); res.json({ PostId: post.id, UserId: req.user.id }); } catch (error) { console.error(error); next(error); } }); router.delete("/:postId", isLoggedIn, async (req, res, next) => { // DELETE /post/10 try { await Post.destroy({ where: { id: req.params.postId, UserId: req.user.id, }, }); res.status(200).json({ PostId: parseInt(req.params.postId, 10) }); } catch (error) { console.error(error); next(error); } }); router.post( "/images", isLoggedIn, upload.array("image"), //PostForm.js에서input에 올린 이미지가 배열로 들어감 (이미지를 여러장 올릴 수 있게 하기 위해서) async (req, res, next) => { // POST /post/images/ //이곳은 이미지 업로드 후 실행되는 부분, 업로드는 위에 upload에서 이미 다 올라감 console.log(req.files); res.json(req.files.map((v) => v.filename)); //프론트로 보내줌 } ); router.post("/:postId/retweet", isLoggedIn, async (req, res, next) => { // POST /post/1/retweet try { const post = await Post.findOne({ where: { id: req.params.postId }, include: [ { model: Post, as: "Retweet", }, ], }); if (!post) { return res.status(403).send("존재하지 않는 게시글입니다."); } if ( req.user.id === post.UserId || (post.Retweet && post.Retweet.UserId === req.user.id) ) { return res.status(403).send("자신의 글은 리트윗할 수 없습니다."); } const retweetTargetId = post.RetweetId || post.id; const exPost = await Post.findOne({ where: { UserId: req.user.id, RetweetId: retweetTargetId, }, }); if (exPost) { return res.status(403).send("이미 리트윗했습니다."); } const retweet = await Post.create({ UserId: req.user.id, RetweetId: retweetTargetId, content: "retweet", }); const retweetWithPrevPost = await Post.findOne({ where: { id: retweet.id }, include: [ { model: Post, as: "Retweet", include: [ { model: User, attributes: ["id", "nickname"], }, { model: Image, }, ], }, { model: User, attributes: ["id", "nickname"], }, { model: Image, }, { model: Comment, include: [ { model: User, attributes: ["id", "nickname"], }, ], }, ], }); res.status(201).json(retweetWithPrevPost); } catch (error) { console.error(error); next(error); } }); module.exports = router; //node에서는 import와 export defau lt를 사용하지 않고 require를 사용한다. //리트윗 case RETWEET_REQUEST: draft.retweetDone = false; draft.retweetLoading = true; draft.retweetError = null; break; case RETWEET_SUCCESS: draft.retweetLoading = false; draft.retweetDone = true; draft.mainPosts.unshift(action.data); break; case RETWEET_FAILURE: draft.retweetLoading = false; draft.retweetError = action.error; break; default: break; } }); export default reducer;//리트윗 function retweetAPI(data) { return axios.post(`/post/${data}/retweet`); //data는 formdata다 } function* retweet(action) { try { const result = yield call(retweetAPI, action.data); yield put({ type: RETWEET_SUCCESS, data: result.data, }); } catch (err) { console.error(err); yield put({ type: RETWEET_FAILURE, error: err.response.data }); } }
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
[공유] input 속성에 hidden 이 적용 안될 때
<input type='file' multiple style={{ display: 'none' }} />display: 'none'으로 줘보세요!
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
faker 적용 후 이미지가 깨지는 오류, 게시글 이미지 클릭 시 이미지 확대 화면의 fill-rule(fillRule) 에러에 관한 질문
안녕하세요! React로 NodeBird SNS 만들기섹션3 Redux-saga 연동까지 모두 수강한 수강생입니다!사전에 제로초님 강의와 트위터 클론 깃허브를 확인하였고, 구글링 및 에러를 번역했습니다!항상 강의 잘 보고 있습니다! 제로초님 항상 감사합니다!(1) 아래 질문 글과 동일하게 faker 이미지가 깨지는 현상이 발생했습니다!https://www.inflearn.com/course/lecture?courseSlug=%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC&unitId=48831&category=questionDetail&tab=community&q=451330lorempixel 사이트 고장이 원인이라 다른 대체 사이트(placeimg)를 찾아봤지만 해결이 되지 않아 질문 올립니다!옆의 에러를 번역해보니 사이트 자체에서 이미지 찾을 수 없다고 합니다!+++(2) Warning: Invalid DOM property fill-rule. Did you mean fillRule? 에러게시글 이미지 클릭 시 콘솔에 fill-rule 에러가 발생합니다!faker 적용 전, 게시글 더미이미지가 있었을 때도 똑같은 에러가 발생하였습니다!더미이미지 삭제 전에도 진행 자체에 문제가 없고, 콘솔에 오류만 출력 되었는데 그냥 넘어가도 되는 에러인지 궁금합니다!(svg 관련 이미지는 적용한 적이 없습니다!)에러를 번역해보니 다음과 같았습니다.
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
로그인 실패 에러 (500 Internal Server Error)
회원가입 시 MySQL 테이블에 이메일, 비밀번호 저장되는 것을 확인하고 로그인을 시도했는데 계속 실패해서 질문드립니다 MySQL 워크벤치로 확인한 user 테이블을 보면 회원가입은 잘 되었는데 id: 2 의 메일, 비밀번호로 로그인이 안 됩니다 console.log로 확인해보니까 패스포트 로그인 시도에서 에러가 나는 것 같기는 한데 정확한 원인을 못 찾겠습니다.. 터미널에 뜨는 메시지입니다개발자도구_네트워크 화면입니다개발자도구_콘솔 화면입니다 의심스러운 코드입니다!routes/user.js 코드const express = require('express'); const bcrypt = require('bcrypt'); const passport = require('passport'); const { User } = require('../models'); const router = express.Router(); router.post('/login', (req, res, next) => { passport.authenticate('local', (err, user, info) => { if (err) { //서버쪽 에러 console.error(err); console.log('routes/user_server err') return next(err); } if (info) { //클라이언트쪽 에러 return res.status(401).send(info.reason); } return req.login(user, async (loginErr) => { if (loginErr) { //패스포트 로그인 에러 console.error(loginErr); console.log('routes/user_loginErr') return next(loginErr); } // res.setHeader('Cookie', 'cxlhy..랜덤토큰') return res.status(200).json(user); }); })(req, res, next); }); router.post('/', async (req, res, next) => { // POST /user try { const exUser = await User.findOne({ where: { email: req.body.email, } }); if (exUser) { return res.status(403).send('이미 가입된 메일입니다.'); } const hashedPassword = await bcrypt.hash(req.body.password, 10); //10~13 await User.create({ email: req.body.email, nickname: req.body.nickname, password: hashedPassword, }); res.status(201).send('OK'); } catch (error) { console.error(error); next(error); //next로 에러 처리 (한방에), status 500 } }); module.exports = router; app.js 코드const express = require('express'); const cors = require('cors'); const session = require('express-session'); const cookieParser = require('cookie-parser'); const passport = require('passport'); const dotenv = require('dotenv'); const postRouter = require('./routes/post'); const userRouter = require('./routes/user'); const db = require('./models'); const passportConfig = require('./passport'); dotenv.config(); const app = express(); db.sequelize.sync() .then(() => { console.log('db 연결 성공'); }) .catch(console.error); passportConfig(); app.use(cors({ origin: 'http://localhost:3000', credentials: false, })); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser(process.env.COOKIE_SECRET)); app.use(session({ saveUninitialized: false, resave: false, secret: process.env.COOKIE_SECRET, })); app.use(passport.initialize()); app.use(passport.session()); ~이하생략~ passport/index.js 코드const passport = require('passport'); const local = require('./local'); const { User } = require('../models'); module.exports = () => { passport.serializeUser((user, done) => { done(null, user.id); }); passport.deserializeUser(async (id, done) => { try { const user = await User.findOne({ where: { id } }); done(null, user); //req.user } catch (error) { console.error(error); done(error); } }); local(); }; passport/local.js 코드const passport = require('passport'); const { Strategy: LocalStrategy } = require('passport-local'); const bcrypt = require('bcrypt'); const { User } = require('../models'); module.exports = () => { passport.use(new LocalStrategy({ usernameField: 'email', //id칸 passwordField: 'password', //비밀번호칸 }, async (email, password, done) => { //done으로 결과 판단 try { const user = await User.findOne({ //가입된 이메일이 있는지 검사 where: { email } }); if (!user) { return done(null, false, { reason: '존재하지 않는 이메일입니다.' }); } const result = await bcrypt.compare(password, user.password) if (result) { return done(null, user); } return done(null, false, { reason: '비밀번호가 일치하지 않습니다.' }); } catch (error) { console.error(error); return done(error); } })); };
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
서버사이드 렌더링 적용 후, css가 풀렸다가 다시 적용되는것 같아요.
안녕하세요!서버사이드 렌더링 적용후에 css가 풀렸다가 다시적용되는 것 같은데, 해결 방법이 있을까요?로그인 후 새로고침을 하면 잠깐 첫번째 화면이 보였다가 두번째 화면을 보여줍니다. 세번째 화면은 리덕스 상태입니다.서버사이드 렌더링을 해서 user와 post의 데이터는 잘 받아오고 있는것 같습니다.
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
faker 사용 후 postData.split is not a function 에러 질문
이전에는 잘 되었는데 faker로 더미데이터 사용 후에 다음과 같은 에러가 뜹니다!faker 버전 이슈를 보고 삭제 후 npm i -D faker@5 로 재설치했는데 오류가 해결되지 않았습니다ㅠ console.log 찍어본 postData의 타입이랑 에러메시지 입니다!4. WrappedApp created new store with withRedux(NodeBird) { initialState: undefined, initialStateFromGSPorGSSR: undefined }type : stringtype : functionTypeError: postData.split is not a functionat PostCardContent (C:\Users\pc\react-nodebird\prepare\front\.next\server\pages\index.js:1131:13)at processChild (C:\Users\pc\react-nodebird\prepare\front\node_modules\react-dom\cjs\react-dom-server.node.development.js:3043:14)at resolve (C:\Users\pc\react-nodebird\prepare\front\node_modules\react-dom\cjs\react-dom-server.node.development.js:2960:5)at ReactDOMServerRenderer.render (C:\Users\pc\react-nodebird\prepare\front\node_modules\react-dom\cjs\react-dom-server.node.development.js:3435:22)at ReactDOMServerRenderer.read (C:\Users\pc\react-nodebird\prepare\front\node_modules\react-dom\cjs\react-dom-server.node.development.js:3373:29)at renderToString (C:\Users\pc\react-nodebird\prepare\front\node_modules\react-dom\cjs\react-dom-server.node.development.js:3988:27)at Object.renderPage (C:\Users\pc\react-nodebird\prepare\front\node_modules\next\dist\next-server\server\render.js:50:851)at Document.getInitialProps (C:\Users\pc\react-nodebird\prepare\front\.next\server\pages\_document.js:264:19)at loadGetInitialProps (C:\Users\pc\react-nodebird\prepare\front\node_modules\next\dist\next-server\lib\utils.js:5:101)at renderToHTML (C:\Users\pc\react-nodebird\prepare\front\node_modules\next\dist\next-server\server\render.js:50:1142) PostCardContent.js 코드 중 의심스러운 부분입니다const PostCardContent = ({ postData }) => ( <div> {postData.split(/(#[^\s#]+)/g).map((v, i) => { if (v.match(/(#[^\s#]+)/)) { return <Link href={`/hashtag/${v.slice(1)}`} key={i}><a>{v}</a></Link>; } return v; })} </div> ); PostCardContent.propTypes = { postData: PropTypes.string.isRequired, };
- 해결됨[리뉴얼] React로 NodeBird SNS 만들기
slick에 이미지가 안 뜨는 오류
상세이미지에서 전체화면까지는 되었는데, 이미지가 안 떠서 질문드립니다 밑에 이미지 Indicator를 보면 스크롤하면 다음으로 넘어가는 기능은 잘 작동하는 것 같은데이미지 불러오기가 안 되는 것 같습니다만 이유를 모르겠습니다.. 컴파일도 잘 되었구요ㅠ ImagesZoom/index.js 코드 첨부합니다const ImagesZoom = ({ images, onClose }) => { const [currentSlide, setCurrentSlide] = useState(0); return ( <Overlay> <Global /> <Header> <h1>상세 이미지</h1> <CloseBtn onClick={onClose}>X</CloseBtn> </Header> <SlickWrapper> <div> <Slick initialSlide={0} beforeChange={(slide) => setCurrentSlide(slide)} infinite arrows={false} slidesToShow={1} slidesToScroll={1} > {images.map((v) => ( <ImgWrapper key={v.src}> <img src={v.src} alt={v.src} /> </ImgWrapper> ))} </Slick> <Indicator> <div> {currentSlide + 1} {' '} / {images.length} </div> </Indicator> </div> </SlickWrapper> </Overlay> ); }; PostImages.js 코드입니다const PostImages = ({ images }) => { const [showImagesZoom, setShowImagesZoom] = useState(false); const onZoom = useCallback(() => { setShowImagesZoom(true); }, []); const onClose = useCallback(() => { setShowImagesZoom(false); }, []); if (images.length === 1) { return ( <> <img role="presentation" src={images[0].src} alt={images[0].src} onClick={onZoom} /> {showImagesZoom && <ImagesZoom images={images} onClose={onClose} />} </> ); } if (images.length === 2) { return ( <> <div> <img style={{ width: "50%", display: 'inline-block' }} role="presentation" src={images[0].src} alt={images[0].src} onClick={onZoom} /> <img style={{ width: "50%", display: 'inline-block' }} role="presentation" src={images[1].src} alt={images[1].src} onClick={onZoom} /> </div> {showImagesZoom && <ImagesZoom images={images} onClose={onClose} />} </> ); } return ( <> <div> <img style={{ width: "50%" }} role="presentation" src={images[0].src} alt={images[0].src} onClick={onZoom} /> <div role="presentation" style={{ display: 'inline-block', width: '50%', textAlign: 'center', verticalAlign: 'middle' }} onClick={onZoom} > <PlusOutlined /> <br /> {images.length - 1}개의 사진 더보기 </div> </div> {showImagesZoom && <ImagesZoom images={images} onClose={onClose} />} </> ); };
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
안녕하세요 제로초님 이미지 업로드 관련 질문이 있습니다.
이미지 업로드를 위한 multer 강의까지 수강을 하였습니다.이미지 업로드를 할때 uploads 폴더에도 이미지가 잘 들어가고UPLOAD_IMAGES_SUCCESS도 잘 나오는 상황입니다.하지만 제로초님의 화면은 제거 버튼과 비록 깨지지만 이미지가 올라간 화면이 보이는데 저는 그 부분이 나오지 않아서 이 점이 궁금하여 질문 드립니다.import React, { useState } from "react"; import { Button, Card, Popover, Avatar, Image, List, Comment } from "antd"; import { RetweetOutlined, HeartOutlined, MessageOutlined, EllipsisOutlined, HeartTwoTone, } from "@ant-design/icons"; import { useCallback } from "react"; import { useSelector, useDispatch } from "react-redux"; import PropTypes from "prop-types"; import PostImages from "./PostImages"; import CommentForm from "./CommentForm"; import PostCardContent from "./PostCardContent"; import { REMOVE_POST_REQUEST, LIKE_POST_REQUEST, UNLIKE_POST_REQUEST, } from "../reducers/post"; import FollowButton from "./FollowButton"; const PostCard = ({ post }) => { //pages/index.js에서 mainPosts에서 하나씩 뜯어서 보내줌 const dispatch = useDispatch(); const [commentFormOpened, setCommentFormOpened] = useState(false); //댓글창 열지 말지 const onLike = useCallback(() => { dispatch({ type: LIKE_POST_REQUEST, data: post.id, }); }, []); //좋아요 const onUnlike = useCallback(() => { dispatch({ type: UNLIKE_POST_REQUEST, data: post.id, }); }, []); //좋아요 취소 const onToggleComment = useCallback(() => { setCommentFormOpened((prev) => !prev); }, []); //폼 버튼 한번 더 누르면 폼 닫기 const onRemovePost = useCallback(() => { return dispatch({ type: REMOVE_POST_REQUEST, data: post.id, }); }, [id]); const id = useSelector((state) => state.user.me?.id); const { removePostloading } = useSelector((state) => state.post); const liked = post.Likers.find((v) => v.id === id); //게시글 좋아요 누른 사람 중에 내가 있는지. return ( <div style={{ marginBottom: 20 }}> <Card cover={post.Images?.[0] && <PostImages images={post.Images} />} //이미지가 존재한다면 PostImages를 출력 actions={[ //카드 아래에 존재하는 것들 <RetweetOutlined key="retweet" />, liked ? ( <HeartTwoTone twoToneColor="red" onClick={onUnlike} /> ) : ( <HeartOutlined key="heart" onClick={onLike} /> ), <MessageOutlined onClick={onToggleComment} key="comment" />, <Popover //더보기 같은 역할 key="more" content={ <Button.Group> {id && post.User.id === id ? ( <> {/* 내가 쓴 글이면 수정, 삭제 */} <Button>수정</Button> <Button type="danger" onClick={onRemovePost} loading={removePostloading} > 삭제 </Button> </> ) : ( // 내가 쓴 글이 아니라면 <Button>신고</Button> )} </Button.Group> } > <EllipsisOutlined /> </Popover>, ]} extra={id && <FollowButton post={post} />} > <Card.Meta //프로필과 내용 등 avatar={<Avatar>{post.User.nickname[0]}</Avatar>} title={post.User.nickname} description={<PostCardContent postData={post.content} />} /> </Card> {commentFormOpened && ( //commentFormOpened가 true이면 열어라 <div> {/* 어떤 게시글에 댓글을 남기는지.. */} <CommentForm post={post} /> <List header={`${post.Comments.length}개의 댓글`} itemLayout="horizontal" dataSource={post.Comments} //데이터는 여기서 가져와서 renderItem={( item //이런식으로 출력한다 ) => ( <li> <Comment author={item.User.nickname} //댓글쓴사람 avatar={ <Avatar>{item.User.nickname[0]}</Avatar> //아바타 } content={item.content} /> </li> )} /> </div> )} </div> ); }; PostCard.PropTypes = { post: PropTypes.shape({ id: PropTypes.number, User: PropTypes.object, content: PropTypes.string, createdAt: PropTypes.string, Comment: PropTypes.arrayOf(PropTypes.object), Images: PropTypes.arrayOf(PropTypes.object), Likers: PropTypes.arrayOf(PropTypes.object), }).isRequired, }; export default PostCard;저의 PostCard 코드입니다 이 코드에서 cover={post.Images?.[0] && <PostImages images={post.Images} />}이 부분이 이미지들을 출력해주는 부분이 아닌가요?? 저의 화면에는 아래처럼 나오지 않습니다.
- 미해결[리뉴얼] React로 NodeBird SNS 만들기
게시글 작성 오류
안녕하세요 제로초님!게시글을 작성하게되면 아래와 같이 성공했다고 응답도 잘 도착하지만 id가 undefined이라고 오류가 납니다.제가 어느 부분을 놓치고 있는건가요??