여기를 참고해서 dotenv-webpack
플러그인을 적용시켰습니다.
.env
파일 수정 시 변경사항을 적용하려면 프론트 서버를 재시작해야 합니다.모든 페이지가 화면에 렌더링 되기 전에 AppLayout.jsx
를 거쳐서 렌더링 되도록 만들고 AppLayout.jsx
에서는 공통 레이아웃을 적용시켰습니다.
이로써 각 페이지마다 NavigationBar
를 적용시킬 필요 없으며, 수정할 때도 AppLayout.jsx
만 변경시켜도 되므로 유지 보수에도 더욱 이점이 있도록 설계했습니다.
import React from "react";
// components
import NavigationBar from "@components/NavigationBar";
// styled-components
import { Wrapper, MainContainer } from "./style";
const AppLayout = ({ children }) => {
return (
<Wrapper>
<NavigationBar />
// children에 각 페이지에 대한 값(jsx)이 들어온다.
<MainContainer>{children}</MainContainer>
</Wrapper>
);
};
export default AppLayout;
로그인과 회원가입 페이지에서 공통적으로 사용하는 컴포넌트들을 추출해서 작은 단위의 컴포넌트로 만들어서 재사용하는 방식으로 구현했습니다.
재사용 컴포넌트들이 많아서 그 중에 하나만 예시로 가져왔습니다.
제 기준으로 재사용 컴포넌트들은 반드시 사이드이펙트 없는 순수한 컴포넌트로 만들고, PropTypes
를 이용해서 전달될 props
의 타입들에 대한 유효성 검사를 실시합니다.
import React from "react";
import Proptypes from "prop-types";
// styled-components
import { Wrapper } from "./style";
const Input = props => {
return <Wrapper {...props} />;
};
Input.propTypes = {
type: Proptypes.string.isRequired,
placeholder: Proptypes.string,
maxLength: Proptypes.number,
value: Proptypes.oneOfType([Proptypes.string, Proptypes.number]).isRequired,
onChange: Proptypes.func.isRequired,
};
export default Input;
styled-components
를 사용하여 css-in-js
방식으로 스타일을 적용했습니다.
하나의 파일에서 styled-components
를 적용해서 스타일이 적용된 컴포넌트를 만들 수 있지만 해당 파일의 라인수가 너무 커지기 때문에 스타일과 컴포넌트에 대한 코드는 분리해서 적용했습니다.
/components/common/Button/index.jsx
와 /components/common/Button/style.js
로 폴더를 이용해서 분리 적용했습니다.
버튼같이 하나의 컴포넌트에서 여러 가지의 스타일을 분리해서 표현해 줘야 하는 컴포넌트의 경우에는 props
값과 styled-components
의 css
를 이용해서 스타일을 적용했습니다. ( 버튼 스타일 - GitHub )
import styled from "styled-components";
export const Wrapper = styled.input`
&[type="text"],
&[type="password"] {
width: 60%;
padding: 0.5rem;
font-size: 1rem;
font-weight: 500;
border: 1px solid purple;
margin-bottom: 0.3rem;
}
&[type="file"] {
display: none;
}
&:focus {
box-shadow: 0 0 3px purple;
}
&::placeholder {
font-size: 0.7rem;
color: rgba(128, 0, 128, 0.5); // purple
}
`;
현재 프로젝트에서 이미지 처리의 방식은 사용자가 이미지를 올리는 즉시 서버로 이미지를 전송해서 저장하고, 저장한 이미지의 이름을 다시 클라이언트로 전송해서 그 이름을 이용해서 프리뷰를 보여주고, 다음 요청에 이미지 이름을 전달하는 방식으로 구현했습니다.
Image
테이블에 추가기본적으로 클라이언트에서 서버로 이미지를 전송할 땐 multipart/form-data
형식으로 전송해줬으며, 서버측에서는 multer
를 이용해서 이미지를 서버의 public
폴더에 저장합니다. ( 추후에는 이미지를 저장하는 위치를 바꿀 예정... 서버에 부담이 심하다고 판단됨 )
import path from "path";
import express from "express";
import multer from "multer";
const __dirname = path.resolve();
const router = express.Router();
const storage = multer.diskStorage({
destination(req, file, done){
done(null, path.join(__dirname, "public", "images"));
},
filename(req, file, done){
const ext = path.extname(file.originalname);
const basename = path.basename(file.originalname, ext);
const filename = basename + "-" + new Date().getTime() + ext;
done(null, filename);
}
});
const limits = { fileSize: 20 * 1024 * 1024 };
const upload = multer({ storage, limits });
router.post("/", upload.array("images"), (req, res) => {
const filenames = req.files.map(file => file.filename);
res.status(201).json({ message: "이미지 생성에 성공하셨습니다.", images: filenames })
});
export default router;
cors
문제 발생react-router-dom
버전 업데이트credentials
: true
를 준 이유는 passport
가 자체적으로 세션쿠키를 사용하기 때문에 브라우저에서 쿠키를 전송해줘야 하기 때문입니다.axios
의 설정으로 withCredentials: true
를 줘서 서버로 쿠키를 전송하도록 설정했습니다.credentials: true
인 경우에는 origin: *
를 주면 보안상의 문제로 오류가 나기 때문에 클라이언트의 주소를 명시했습니다.// app.js에 아래 내용 추가
import cors from "cors";
app.use(cors({
credentials: true,
origin: process.env.CLENT_URL
}));
react-router-dom가 v6으로 업데이트되면서 NavLink
의 사용법이 달라져서 activeStyle
을 적용하지 못한 상태여서 추후에 적용할 예정입니다.
http status 204일 경우에는 응답 데이터가 전송되지 않아서 기존에 204를 사용하던 응답을 모두 200으로 변경했습니다.
현재 프로젝트에서 사용하는 모든 아이콘은 어도비 일러스트를 이용해서 직접 제작해서 사용하고 있습니다.