react-bittersweet
├── client
│ ├── build
│ ├── jsconfig.json
│ ├── node_modules
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ └── src
│ ├── App.js
│ ├── _actions
│ │ ├── types.js
│ │ └── user_action.js
│ ├── _reducers
│ │ ├── index.js
│ │ └── user_reducer.js
│ ├── components
│ │ ├── Footer.js
│ │ ├── Nav.js
│ │ ├── ScrollBtn.js
│ │ ├── faq
│ │ │ └── FormOfFaq.js
│ │ ├── menu
│ │ │ ├── FormOfMenu.js
│ │ │ └── FormOfMenuDetail.js
│ │ ├── store
│ │ │ └── KakaoMap.js
│ │ └── whatsnew
│ │ ├── FormOfNotice.js
│ │ ├── FormOfNoticeDetail.js
│ │ └── FormOfNoticeWrite.js
│ ├── css
│ │ └── App.module.css
│ ├── data
│ │ └── menuData.json
│ ├── hoc
│ │ └── auth.js
│ ├── hooks
│ │ └── useScrollFadeIn.js
│ ├── images
│ ├── index.js
│ ├── routes
│ │ ├── LoginPage.js
│ │ ├── RegisterPage.js
│ │ ├── aboutUs
│ │ │ ├── AboutUs.js
│ │ │ ├── BrandPrinciple.js
│ │ │ ├── Coffee.js
│ │ │ └── History.js
│ │ ├── faq
│ │ │ └── Faq.js
│ │ ├── home
│ │ │ └── Home.js
│ │ ├── menu
│ │ │ ├── Menu.js
│ │ │ ├── MenuBeverage.js
│ │ │ ├── MenuCoffee.js
│ │ │ ├── MenuDetail.js
│ │ │ └── MenuTea.js
│ │ ├── store
│ │ │ └── Store.js
│ │ └── whatsNew
│ │ ├── Notice.js
│ │ ├── NoticeDetail.js
│ │ ├── NoticeEdit.js
│ │ └── NoticeWrite.js
│ └── setupProxy.js
├── node_modules
├── package-lock.json
├── package.json
├── .gitignore
└── server
├── config
│ ├── dev.js
│ ├── key.js
│ └── prod.js
├── index.js
├── middleware
│ └── auth.js
└── models
├── Posting.js
└── User.js
Freenom에서 무료 도메인 생성 후 AWS와 연동
클라이언트는 Build파일 AWS S3에 배포하고 CloudFront와 Route 53 이용 및 ACM(AWS Certificate Manager)을 통해 SSL인증서 발급받고 HTTPS를 적용.
서버는 AWS EC2를 통해 배포하고 SSL인증서 발급 및 AWS 로드밸런서 이용하여 HTTPS 적용
- https://server.bittersweet.tk (서버 연결)
#2.수정된 방식✅
클라이언트와 서버 모두 EC2를 통해 배포 및 도메인 통일
SSL 인증서 발급은 AWS 로드밸런서가 아닌 Certbot, Nginx 및 Let’s Encrypt를 통해 진행
"url": ”images/menu-coffee.jpg”
img src={require(”images/menu-coffee.jpg”)}
img src={require(`${menu.url}`)}
img src={require(`${menu.url}`).default}
// Menu.js
<img src={require(`images/${*menu*.url}`)} />
// menuData.json 수정
"url": "menu-coffee.jpg”
// menuData.json
{
"data": [
{
"idx": "1",
"name_ko": "아메리카노",
"name_en": "Americano",
"desc": "메뉴설명입니다.",
"category": ["ALL", "COFFEE"],
"temperature": "HOT",
"열량": "10",
"나트륨": "5",
"포화지방": "0",
"단백질": "1",
"당류": "0",
"카페인": "150",
"url": "menu-coffee.jpg"
},
]
}
// Menu.js
<ul>
{filterCategory &&
filterCategory.map((menu) => {
return (
<div className={styles.menu_div} key={menu.idx}>
<li>
<Link to={`/menu-detail/${menu.idx}`}>
<img src={require(`images/${menu.url}`)} alt={menu.url} />
</Link>
</li>
<span>{menu.name_ko}</span>
</div>
)
})}
</ul>
<button>🔓LOG-OUT</button>
인 경우에는 정상적으로 출력.GET http://localhost:3000/생략 504 (Gateway Timeout)
Uncaught (in promise) AxiosError {message: 'Request failed with status code 504', name: 'AxiosError', code: 'ERR_BAD_RESPONSE', config: {…}, request: XMLHttpRequest, …}
// server/index.js
// 서버 측에서 작성한 UserName 보내기위한 코드
// =====Get User Name=====
app.get("/api/users/username", auth, (req, res) => {
User.findById({ _id: req.user._id }, (err, user) => {
if (err) return res.json({ success: false, err });
// if (err) {
// return res.send("로그인");
// }
return res.status(200).send(req.user.name);
});
});
// client/Nav.js
// 클라이언트의 Nav Bar의 로그인/로그아웃 버튼 구현
function Nav() {
const navigate = useNavigate();
const [isLogin, setIsLogin] = useState(false);
const [logInUserName, setLogInUserName] = useState("");
const onClickHandler = () => {
axios.get("/api/users/logout").then((response) => {
if (response.data.success) {
navigate("/");
alert("로그아웃 하였습니다.");
setIsLogin(true);
} else {
alert("로그인 페이지로 이동합니다.");
}
});
};
const getUserName = () => {
axios.get("/api/users/username").then((response) => {
let user = response.data;
console.log(`유저네임: ${user}`);
if (user.toString().includes("object")) {
setIsLogin(true);
setLogInUserName("🔐LOG-IN");
} else if (user) {
setIsLogin(false);
// setLogInUserName(`${user}님 🔓LOG-OUT`);
}
});
};
useEffect(() => {
getUserName();
}, [isLogin, logInUserName]);
return (
<div>
{/* ...생략 */}
{isLogin ? (
<Link to={`/login`}>
<button onClick={onClickHandler}>
{logInUserName}
</button>
</Link>
) : (
<button onClick={onClickHandler}>
🔓LOG-OUT
</button>
)}
{/* ...생략 */}
</div>
client 폴더에 .env.development
파일과 .env.production
파일을 package.json 파일과 같은 경로에 생성하여 아래와 같이 환경변수 설정.
- .env.development는 개발 로컬호스트로 그대로 적용하기 위해 공란으로 남김. 이렇게 되면 기존에 http-proxy-middleware 라이브러리를 통해 설정해둔 localhost:5000(개발 환경에서의 서버단의 포트번호)으로 적용되어 연결된다.
- .env.production은 배포 후 서버 url을 입력하고, 클라이언트에서 axios를 통해 api를 호출 시, 해당 환경변수를 앞에 추가해줘야 했다.
- 환경변수 선언 시 지켜야할 규칙: 꼭 REACT_APP 으로 시작되어야 함.
//client 폴더 > .env.delvopment
# process.env.REACT_APP_HOST
REACT_APP_HOST=""
//client 폴더 > .env.production
# process.env.REACT_APP_HOST
REACT_APP_HOST=https://server.bittersweet.tk
//client에서 환경변수 추가
const getPosts = () => {
axios.get(`${process.env.REACT_APP_HOST}/api/posting`).then((response) => {
let post = response.data;
setPosts(post);
});
};
key.js (환경변수 조건문 설정)
if (process.env.NODE_ENV === "production") {
module.exports = require("./prod");
} else {
module.exports = require("./dev");
}
dev.js (개발 환경): mongoDB localhost 27017 포트 번호로 로컬에 생성한 DB로 연결.
module.exports = {
mongoURI: "mongodb://localhost:27017/생성한DB명",
};
module.exports = {
mongoURI: process.env.MONGO_URI,
};
vi ~/.bashrc
export NODE_ENV="production"
export MONGO_URI="mongodb+..."
source ~/.bashrc
Access to XMLHttpRequest at '[https://server.bittersweet.tk/api/users](https://server.bittersweet.tk/api/users)' from origin '[https://www.bittersweet.tk](https://www.bittersweet.tk/)' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
npm i cors
//server 폴더 > index.js
const cors = require("cors");
//CORS ISSUE
const clientURL = ["https://bittersweet.tk", "https://www.bittersweet.tk"];
let corsOptions = {
origin: function (origin, callback) {
if (clientURL.indexOf(origin) !== -1) {
//URL배열에 origin 인자가 있을 경우
callback(null, true); //cors 허용
} else {
callback(new Error("Not allowed by CORS")); //cors 비허용
}
},
credentials: true,
};
app.use(cors(corsOptions));
//server 폴더 > 수정된 index.js
const cors = require("cors");
let corsOptions = {
origin: [
"http://localhost:3000",
"https://bittersweet.ml",
"https://www.bittersweet.ml",
],
credentials: true,
};
app.use(cors(corsOptions));
withCredentials: true
를 쿠키를 받을 요청 뿐만 아니라 쿠키를 보낼 요청 또한 withCredentials 옵션을 true로 설정해줬다.//client > user.action.js
export function loginUser(dataToSubmit) {
//dataToSubmit은 LoginPage의 body(이메일, 패스워드)를 parameter로 받는 것임.
const request = axios
.post(`${process.env.REACT_APP_HOST}/api/users/login`, dataToSubmit, {
withCredentials: true,
})
.then((response) => response.data);
return {
type: LOGIN_USER,
payload: request,
};
}
export function auth() {
const request = axios
.get(`${process.env.REACT_APP_HOST}/api/users/auth`, {
withCredentials: true,
})
.then((response) => response.data);
return {
type: AUTH_USER,
payload: request,
};
}
//client > Nav.js
const onClickHandler = () => {
axios
.get(`${process.env.REACT_APP_HOST}/api/users/logout`, {
withCredentials: true,
})
.then((response) => {
if (response.data.success) {
navigate("/");
alert("로그아웃 하였습니다.");
setIsLogin(true);
}
});
};
const getUserName = () => {
axios
.get(`${process.env.REACT_APP_HOST}/api/users`, {
withCredentials: true,
})
.then((response) => {
let user = response.data;
console.log(user);
if (user.toString().includes("object")) {
setIsLogin(true);
setLogInUserName("🔐LOG-IN");
} else {
setIsLogin(false);
// setLogInUserName(`${user}님 🔓LOG-OUT`);
}
});
};
/static/media/.jpg
**/static/media/.png
/index.html