여기를 참고해서 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으로 변경했습니다.
현재 프로젝트에서 사용하는 모든 아이콘은 어도비 일러스트를 이용해서 직접 제작해서 사용하고 있습니다.