Weather๐ŸŒค๏ธ ํ”„๋กœ์ ํŠธ(with react, redux, node.js) ํ›„๊ธฐ

BadaHertz52ยท2023๋…„ 2์›” 7์ผ
2

project

๋ชฉ๋ก ๋ณด๊ธฐ
6/6
post-thumbnail

1. ํ”„๋กœ์ ํŠธ

1) ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

"weather" ๋Š” react, redux, redux-middleware(thunk, saga)๋ฅผ ์‚ฌ์šฉํ•ด kakao local REST API์™€ ๊ณต๊ณต ๋ฐ์ดํ„ฐ ํฌํ„ธ์—์„œ ์ œ๊ณต๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ์‚ฌ์šฉ์ž์˜ ํ˜„์žฌ ์œ„์น˜์— ๋”ฐ๋ฅธ ๊ตญ๋‚ด์˜ ๋‚ ์”จ ์ •๋ณด๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ๋‚ ์”จ ์ •๋ณด ์‚ฌ์ดํŠธ๋กœ ์‚ฌ์šฉ์ž๋Š” ํ˜„์žฌ์˜ ์ž์‹ ์˜ ์œ„์น˜์— ๋”ฐ๋ฅธ ํ˜„ ์‹œ์ ์˜ ๋‚ ์”จ, ์•ž์œผ๋กœ 3์ผ ์ด๋‚ด์˜ ์‹œ๊ฐ„๋ณ„ ๋‚ ์”จ, ์ผ์ฃผ์ผ๊ฐ„์˜ ์ „๊ตญ ๋‚ ์”จ, ์ผ์ถœ ์ผ๋ชฐ ์‹œ๊ฐ์„ ์ œ๊ณต๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. weather์˜ ์›น๋””์ž์ธ์€ ๋„ค์ด๋ฒ„ ๋‚ ์”จ๋ฅผ ์ฐธ๊ณ ํ–ˆ๋‹ค.

weather project ์‹คํ–‰

2) ํ”„๋กœ์ ํŠธ ๋ชฉ์ 

weather ํ”„๋กœ์ ํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‹œ๋„๋“ค์„ ํ•ด๋ณด๊ธฐ ์œ„ํ•ด ์‹œ์ž‘๋œ ํ”„๋กœ์ ํŠธ์ด๋‹ค.

  • CORS ์ •์ฑ…์„ ์ค€์ˆ˜ํ•˜๋ฉฐ ์™ธ๋ถ€์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ REST FULํ•˜๊ฒŒ ๋ฐ›์•„์˜ค๊ธฐ

  • redux-toolkit , redux-middleware์ธ thunk ์™€ saga ์˜ ์ฐจ์ด์ ์„ ๊ฒฝํ—˜ํ•ด๋ณด๊ณ  ๊ฐ๊ฐ์˜ ์žฅ๋‹จ์ ๊ณผ ์–ธ์ œ ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š”์ง€ ์ƒ๊ฐํ•ด๋ณด๊ธฐ

  • Github์˜ Actions ์™€ Secrets๋ฅผ ์‚ฌ์šฉํ•ด API key ๋ฅผ ๊นƒํ—™์— ์˜ฌ๋ฆฌ์ง€ ์•Š๊ณ ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ธฐ

  • table,th,td,tr์˜ ํƒœ๊ทธ ์‚ฌ์šฉ์— ์ต์ˆ™ํ•ด์ง€๊ธฐ

  • ์‹œ๊ฐ„๋ณ„ ๋‚ ์”จ๋ฅผ ๊ทธ๋ž˜ํ”„๋กœ ํ‘œํ˜„ํ•ด๋ณด๊ธฐ

2.๋ฐฐ์šด ์ .๋Š๋‚€์ 

1) ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ ๋ถ„์„

โ˜๏ธweather ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉ๋œ API

  • ๊ธฐ์ƒ์ฒญ ๋‹จ๊ธฐ์˜ˆ๋ณด : ์ดˆ๋‹จ๊ธฐ ์‹คํ™ฉ, ์ดˆ๋‹จ๊ธฐ ์˜ˆ๋ณด, ๋‹จ๊ธฐ ์˜ˆ๋ณด
  • ๊ธฐ์ƒ์ • ์ค‘๊ธฐ์˜ˆ๋ณด :์ค‘๊ธฐ ์œก์ƒ ์˜ˆ๋ณด, ์ค‘๊ธฐ ๊ธฐ์˜จ ์˜ˆ๋ณด
  • ์—์–ด์ฝ”๋ฆฌ์•„ ๋Œ€๊ธฐ์˜ค์—ผ์ •๋ณด : ์‹œ๋„๋ณ„ ์‹ค์‹œ๊ฐ„ ์ธก์ •์ •๋ณด
  • ํ•œ๊ตญ ์ฒœ๋ฌธ์—ฐ๊ตฌ์› ์ผ์ถœ์ผ๋ชฐ์‹œ๊ฐ์ •๋ณด
  • kakao local REST API : ์œ„์น˜์˜ ํ–‰์ •๊ตฌ์—ญ ์ •๋ณด

rootState์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ์˜ ํ˜•์‹์„ ๋‚ด๊ฐ€ ์ง์ ‘ ๋งŒ๋“ค์—ˆ๋˜ ๋•Œ์™€๋Š” ๋‹ฌ๋ฆฌ ์ด๋ฒˆ weather ํ”„๋กœ์ ํŠธ๋Š” ํƒ€์ธ์ด ๋งŒ๋“  ์—ฌ๋Ÿฌ ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ๋“ค์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.
๋‚ด๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค๋•Œ ์™€๋Š” ๋‹ฌ๋ฆฌ API ๋งˆ๋‹ค ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ์š”์ฒญ ์กฐ๊ฑด(ex: ์˜ˆ์‹œ1)๊ณผ ๋ฐ์ดํ„ฐ ํ˜•์‹์„ ๋ถ„์„ํ•˜๊ณ  ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ํ˜•์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์žฌ๊ฐ€๊ณตํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์ณ์•ผํ–ˆ๋‹ค.

์˜ˆ์‹œ1) baseDate๋กœ ์“ฐ์ด์ง€๋งŒ, API๋งˆ๋‹ค ๋‹ค๋ฅธ baseDate๊ฐ€ ํ•„์š”ํ–ˆ์Œ

//changeBaseDate : Date๋ฅผ string type ์˜ "yyyymmdd" ํ˜•ํƒœ๋กœ ๋ฐ”๊พธ์–ด์ฃผ๋Š” ํ•จ์ˆ˜ 

// ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹œ๊ฐ์— ๋”ฐ๋ผ baseDate์˜ ๊ธฐ์ค€์„ ์˜ค๋Š˜๋กœ ํ•  ์ง€ ์–ด์ œ๋กœ ํ•  ์ง€๊ฐ€ ๋‹ฌ๋ผ์ง
  const baseDate_today =changeBaseDate(today);
  const baseDate_yesterday =getYesterDay(date);
// ํ•˜๋Š˜์ƒํƒœ๋ฅผ ์กฐํšŒํ•  ๋•Œ ํ•„์š”ํ•œ baseDate
  const baseDate_skyCode = minutes < 30 && hours === 0 ? 
                          baseDate_yesterday
                          :
                          baseDate_today ;
//๋‹จ๊ธฐ์˜ˆ๋ณด ์กฐํšŒ์— ํ•„์š”ํ•œ baseDate
  const baseDate_svf = hours < 2 ? baseDate_yesterday :baseDate_today;
  

navigator๋ฅผ ์ด์šฉํ•œ ์‚ฌ์šฉ์ž์˜ ์œ„๋„, ๊ฒฝ๋„๊ฐ’์ด ๊ธฐ์ƒ์ฒญ์—์„œ ์ œ๊ณตํ•œ ์œ„์น˜ ๋ฐ์ดํ„ฐ ์ž๋ฃŒ์™€ ์ผ์น˜ํ•˜์ง€ ์•Š์•„์„œ ์นด์นด์˜ค API๋ฅผ ํ™œ์šฉํ•ด ์‚ฌ์šฉ์ž์˜ ์œ„์น˜๊ฐ’์— ๋”ฐ๋ฅธ ํ–‰์ •๊ตฌ์—ญ๋ช…์„ ์ฐพ์•„๋‚ด๊ณ  ์ด๋ฅผ ํ†ตํ•ด ๋‚ ์”จ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ• ๋•Œ ํ•„์š”ํ•œ ๊ฐ’๋“ค์„ ๊ตฌํ•ด์•ผํ–ˆ๋‹ค.

2)API Key ๋ณด์•ˆ

ํ”„๋กœ์ ํŠธ๋ฅผ ๊นƒํ—™์— ๋ฐฐํฌํ•˜๋˜ ๋ณด์•ˆ์„ ์œ„ํ•ด API Key๋Š” ๊นƒํ—™์— ๋ฐฐํฌํ•˜์ง€ ์•Š์œผ๋ฉด์„œ๋„ ๋ฐฐํฌ๋œ ํ”„๋กœ์ ํŠธ์—์„œ API์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์„ ํƒํ•œ ๋ฐฉ๋ฒ•์€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” .env์— ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ API Key๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ๋ฐฐํฌํ™˜๊ฒฝ์—์„œ๋Š” Github Secrets์—์„œ ์ž‘์„ฑํ•œ key๋ฅผ Github Actions๋ฅผ ํ†ตํ•ด ๋ฐฐํฌ ํ™˜๊ฒฝ์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด์˜€๋‹ค.

๐Ÿ‘ฉโ€๐Ÿ’ป"React(CRA)๊ฐœ๋ฐœ ์‹œ API Key์„ค์ •๋ณด์•ˆ" ์— ๋Œ€ํ•œ ๋ธ”๋กœ๊ทธ ๊ธ€ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ

3) redux-toolkit, redux-thunk, redux-saga

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ํ†ตํ•ด redux-toolkit, redux-thunk์™€ redux-saga๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ์ด ์…‹์˜ ์žฅ๋‹จ์ ๊ณผ ์ฐจ์ด์ ์„ ์•Œ์•„๋ณด๊ณ  ์‹ถ์—ˆ๋‹ค.
์ฝ”๋“œ์˜ ๊ฒฐ๊ณผ๋ฌธ์—์„œ๋Š” ์ฒด๊ฐ์ ์„ ๋Š๊ปด์ง€๋Š” ์ฐจ์ด๋Š” ์—†์—ˆ๋‹ค.

  • redux-toolkit, redux-thunk ,redux-saga๋ฅผ ์‚ฌ์šฉํ•œ ํ•จ์ˆ˜
positionweather
tooklit(createAsycnThunk ์‚ฌ์šฉ)toolkitPositiontoolkitWeather
thunkgetPositionThunkgetWeatherThunk
sagagetPositionSaga, positionSagagetWeatherSaga, weatherSaga

๊ทธ๋Ÿฌ๋‚˜ action์˜ type์— ์žˆ์–ด์„œ๋Š” ์ฐจ์ด๊ฐ€ ์žˆ์—ˆ๋‹ค.
action type์„ ๋‹ค์Œ์˜ 4๊ฐ€์ง€๋กœ ์„ค์ •ํ•ด์„œ ์ž‘์—…์„ ์ง„ํ–‰ํ–ˆ๋‹ค.

  • reset: ์ƒํƒœ ์ดˆ๊ธฐํ™”
  • request: ๋ฐ์ดํ„ฐ ์š”์ฒญ
  • success: ๋ฐ์ดํ„ฐ ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐ›์Œ
  • failur: ๋ฐ์ดํ„ฐ ์š”์ฒญ ์‹คํŒจ

redux-middleware๋Š” createSlice์˜ ๋ฆฌ๋“€์„œ์—์„œ ์ •ํ•œ ๊ฒƒ๋“ค์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ createAsynThunk ๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์— ๋”ฐ๋ผ "pending, fulfilled, rejected"์˜ ์•ก์…˜ํƒ€์ž…์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๊ธฐ๋•Œ๋ฌธ์— ์ผ๋ฐ˜์ ์ธ ๋ฆฌ๋“€์„œ์™€๋Š” ๋‹ค๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•ด์ฃผ์–ด์•ผํ•ด์„œ extraReducers ์„ ์‚ฌ์šฉํ•ด ๋ฆฌ๋“€์„œ๋ฅผ ์ฒ˜๋ฆฌํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค.

weather reducer

export const weatherSlice =createSlice({
  name:"weather",
  initialState,
  //thunk, saga
  reducers :{
    reset :(state)=>({
      ...noneState_weather
    }),
    request :(state , action:PayloadAction<PositionSuccessData>)=>({
        ...noneState_weather,
        state:"pending"
      }),
    success :(state, action:PayloadAction<WeatherState>) =>({
      ...action.payload,
      state:"success"
    }),
    failure : (state, action:PayloadAction< Error>)=>({
      ...noneState_weather,
      state:"failure",
      error:action.payload
    })
  },
  //toolkitWeather 
  extraReducers(builder) {
    builder
    .addCase( toolkitWeather.pending , (state, action)=>{
      return {
        ...noneState_weather,
        state:"pending"
      }
    })
    .addCase(toolkitWeather.fulfilled , (state, action)=>{
      return{
        ...action.payload
      }
    })
    .addCase(toolkitWeather.rejected, (state, action)=>{
      return {
        ...noneState_weather,
        state:"failure",
        error:action.error as Error
      }
    })
  }
});

๊ทธ๋ฆฌ๊ณ  ๊ฐ™์€ weahterAction ์ด๋ผ๋Š” action์„ ์‚ฌ์šฉํ•˜์ง€๋งŒ, dispatch์˜ ํƒ€์ž…์„ ์…‹ ๋‹ค ๋‹ค๋ฅด๊ฒŒ ํ•ด์ค˜์•ผํ–ˆ๋‹ค.

dispatch type

//redux-thunk
const weatherThunkDispatch =useDispatch<ThunkDispatch<WeatherState, unknown,WeatherAction>>();

//redux-saga
  const dispatch =useDispatch();

//redux-toolkit
  const toolkitDispatch =useDispatch<ThunkDispatch<PositionState|WeatherState, CurrentPosition|PositionSuccessData, AnyAction>>();

action dispatch

if(startThunk.current){
       // redux-thunk ์ด์šฉ  
  weatherThunkDispatch(getWeatherThunk(positionSuccessDate))
        startThunk.current = false; 
      }else if(startSaga.current){
        // redux-saga ์ด์šฉ  
       dispatch(weatherActions.request(positionSuccessDate));
        startSaga.current =false;
      }else{
         // redux-toolkit ์ด์šฉ
        startToolkit.current =false;
        toolkitDispatch(toolkitWeather(positionSuccessDate))
      }

๊ทธ๋ฆฌ๊ณ  ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ๋‚ด์—์„œ ์ฝ”๋“œ ์‹คํ–‰ ๊ฒฐ๊ณผ์— ์žˆ์–ด์„œ ๋ณ„๋‹ค๋ฅธ ์ฐจ์ด์ ์ด ์—†์—ˆ์ง€๋งŒ redux-toolkit์€ redux์˜ ํŽธํ•œ ์‚ฌ์šฉ์„ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ๊ณ , redux-middleware๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ๋ชฉ์ ์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ณด๋‹ค ๋ณต์žกํ•œ ๋น„๋™๊ธฐ ์ž‘์—…์— ์žˆ์–ด์„œ๋Š” redux-middleware๊ฐ€ ๋” ์ ํ•ฉํ•˜๋‹ค.

๐ŸŒŸ์ •๋ฆฌ

  • ์ฝ”๋“œ ์‹คํ–‰ ๊ฒฐ๊ณผ์—์„œ๋Š” ํฐ ์ฐจ์ด์ ์„ ๋Š๋ผ์ง€ ๋ชปํ–ˆ๋‹ค.
  • reducer ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค๋•Œ toolkit์„ ํ™œ์šฉํ•œ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•ด extraReducers๋ฅผ ๋งŒ๋“ค์–ด์ฃผ์–ดํ– ํ–ˆ๋‹ค.
  • dispatch์˜ ํƒ€์ž…์„ ๋‹ค๋ฅด๊ฒŒ ์„ค์ •ํ•ด์•ผ ํ–ˆ๋‹ค.
  • ๋ณด๋‹ค ๋ณต์žกํ•œ ๋น„๋™๊ธฐ ์ž‘์—…์—์„œ๋Š” redux-middleware๊ฐ€ ๋” ์ ํ•ฉํ•˜๋‹ค.

๐Ÿ‘ฉโ€๐Ÿ’ป redux-thunk ์™€ redux-saga๋ฅผ ๋น„๊ตํ•œ ๋ธ”๋กœ๊ทธ ๊ธ€ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ

4) reducer ๊ฐ„ํŽธํ•˜๊ฒŒ ์ž‘์„ฑํ•˜๊ธฐ

:redux-tookit ๊ณผ typesafe-actions

action์™€ reducer๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ๋“ค๋กœ typesafe-actions์™€ redux-tooklit ๋“ฑ์ด ์žˆ๋‹ค. ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋ฉด์„œ ๋‘˜ ๋‹ค ์‚ฌ์šฉํ•ด๋ดค๋Š”๋ฐ, redux-toolkit ๋งŒ์œผ๋กœ๋„ ์ถฉ๋ถ„ํ•ด์„œ ๋ณ„๋„๋กœ ์„ค์น˜ํ•ด์•ผํ•˜๋Š” typesafe-actions ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ๋„ ํ–ˆ๋‹ค.

๐Ÿ–ฑ๏ธ redux-toolkit ์‚ฌ์ดํŠธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

5) Chart.js์‚ฌ์šฉ

๐Ÿ“ˆ Chart.js ์‚ฌ์ดํŠธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐Ÿ“ˆ Chart.js ๋ฅผ ์‚ฌ์šฉํ•ด ๋งŒ๋“  ๊ธฐ์˜จ ๊ทธ๋ž˜ํ”„

๐Ÿ“ˆ Chart.js ๋กœ ๋งŒ๋“  ๊ทธ๋ž˜ํ”„ ์ฝ”๋“œ

์‹œ๊ฐ„๋ณ„ ๊ธฐ์˜จ์— ๋Œ€ํ•œ ๊ทธ๋ž˜ํ”„๋ฅผ Chart.js๋ฅผ ์‚ฌ์šฉํ•ด ๋งŒ๋“ค์—ˆ๋‹ค.
์ฒ˜์Œ ์‚ฌ์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜€๋‹ค.
์ตœ์ € ๊ธฐ์˜จ๊ณผ ์ตœ๋Œ€ ๊ธฐ์˜จ์„ ๊ธฐ์ค€์œผ๋กœ ํ•œ y์ถ•, ์‹œ๊ฐ„๋ณ„ x์ถ•์˜ ํญ ๋“ฑ ์ƒ๊ฐ๋ณด๋‹ค ์ž˜ ๋งŒ๋“ค์—ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์•„์‰ฌ์›€๋„ ์žˆ์—ˆ๋‹ค.Chart.js๋Š” canvas์— ๊ทธ๋ž˜ํ”„ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋ฆฌ๋Š” ๋ฐฉ์‹์ด๋ผ weather ํ”„๋กœ์ ํŠธ์ฒ˜๋Ÿผ x์ถ•๊ณผ y์ถ•์˜ ๊ธธ์ด๊ฐ€ ์ฐจ์ด๊ฐ€ ๋งŽ์ด ๋‚˜๋ฉด ๊ทธ๋ž˜ํ”„ ์ด๋ฏธ์ง€๊ฐ€ ๋Š˜์–ด๋‚˜์„œ ๊ทธ๋ž˜ํ”„ ์„ ๊ณผ ๊ทธ๋ž˜ํ”„์˜ ์ˆ˜์น˜๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๋ถ€๋ถ„์ด ๊นจ์ง€๋Š” ๋ฌธ์ œ์ ์ด ์žˆ์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๊ธฐ์˜จ์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ˆ˜์น˜์˜ ๊ฒฝ์šฐ,Chart.js ๋ฅผ ์ด์šฉํ•˜์ง€ ์•Š๊ณ  ๊ฐ ์‹œ๊ฐ„๋Œ€๋ณ„ ์˜จ๋„์— ๋”ฐ๋ฅด y๊ฐ’์„ ๊ณ„์‚ฐํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋”ฐ๋กœ element๋ฅผ ์ƒ์„ฑํ•ด์„œ ํ‘œํ˜„ํ–ˆ๋‹ค.

6) ์ฐจ๋‹จ๋จ(mixed-content) error

: Node.js ๊ณต๋ถ€ ํ•„์š”์„ฑ์„ ๋Š๋‚Œ

๊ฐœ๋ฐœํ™˜๊ฒฝ์—์„œ๋Š” ๊ดœ์ฐฎ์•˜์ง€๋งŒ ๋ฐฐํฌ ํ™˜๊ฒฝ์—์„œ๋Š” API์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ๋•Œ ์ฐจ๋‹จ๋จ(mixed-content) ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

A. mixed content(ํ˜ผํ•ฉ ์ฝ˜ํ…์ธ )?

mixed content(ํ˜ผํ•ฉ ์ฝ˜ํ…์ธ )๋Š” HTTP ์ฝ˜ํ…์ธ ์™€ HTTPS ์ฝ˜ํ…์ธ ๊ฐ€ ํ•จ๊ป˜ ๋กœ๋“œ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํฌ๋กฌ๊ณผ ํŒŒ์ด์–ดํญ์Šค๋“ฑ ์›น๋ธŒ๋ผ์šฐ์ €์—์„œ ์•ˆ์ „์ƒ์˜ ์ด์œ ๋กœ https ํŽ˜์ด์ง€์—์„œ HTTPS๊ฐ€ ์•„๋‹Œ HTTP ๋กœ ๋ฐ›์•„์˜ค๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ์ฐจ๋‹จํ•˜๊ณ  ์žˆ๋‹ค.

B. mixed content ์—๋Ÿฌ ์›์ธ

weather ํŽ˜์ด์ง€์—์„œ mixed-content ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ์ด์œ ๋Š” weather ํŽ˜์ด์ง€๋Š” HTTPS ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ ์ผ์ถœ์ผ๋ชฐ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ์™€ ๋ฏธ์„ธ๋จผ์ง€,์ดˆ๋ฏธ์„ธ๋จผ์ง€์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋Š” http์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ด์˜€๋‹ค.

C. mixed-content ์—๋Ÿฌ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

โ‘  API๋ฅผ HTTP๊ฐ€ ์•„๋‹Œ HTTPS ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•˜๋„๋ก API ์ œ๊ณต์ž๊ฐ€ ์ˆ˜์ •
โ‘ก ๋ฐฑ์—”๋“œ์—์„œ API ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€ ํ”„๋ก ํŠธ์—”๋“œ์— ๋„˜๊ธฐ๊ธฐ
โ‘ข META ํƒœ๊ทธ๋ฅผ ํ—ค๋”์— ์ถ”๊ฐ€ํ•˜์—ฌ, http ์ฝ˜ํ…์ธ ๋ฅผ ์ž๋™์œผ๋กœ https๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋กœ๋”ฉํ•˜๊ธฐ

<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"> 

C-โ‘ ์€ API ์ œ๊ณต์ž๊ฐ€ ํ•ด์•ผํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๋‚ด๊ฐ€ ํ•  ์ˆ˜ ์—†๊ณ , C-โ‘ข์„ ์‹œ๋„ํ–ˆ์ง€๋งŒ ์˜ค๋ฅ˜๊ฐ€ ํ•ด๊ฒฐ๋˜์ง€๋Š” ์•Š์•˜๋‹ค. ๊ทธ๋ž˜์„œ C-โ‘ก์˜ ๋ฐฉ๋ฒ•์œผ๋กœ ์˜ค๋ฅ˜๋ฅผ ํ•ด๊ฒฐํ•ด์•ผํ•œ๋‹ค๋Š” ๊ฒฐ๋ก ์„ ๋‚ด๋ ธ๊ณ  Node.js๋ฅผ ๊ณต๋ถ€ํ•ด์•ผ ํ•˜๋Š” ํ•„์š”์„ฑ์„ ๋” ๋Š๋ผ๊ฒŒ ๋˜์—ˆ๋‹ค.

D. Node.js๋ฅผ ํ†ตํ•ด mixed-content ์˜ค๋ฅ˜ ํ•ด๊ฒฐํ•˜๊ธฐ

mixed-content๋ฅผ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ์œ„ํ•ด Node.js ์™€ express ๋ชจ๋“ˆ๋ฅผ ์ด์šฉํ•ด node ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค๊ณ  ์„œ๋ฒ„์—์„œ ์™ธ๋ถ€ api์— ์š”์ฒญ์„ ๋ณด๋‚ด ๋‚ ์”จ ์ •๋ณด์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜จ ํ›„ ์ด๋ฅผ ํ”„๋ก ํŠธ์— ๋„˜๊ฒจ์ฃผ๋Š” ๋ฐฉ์‹์„ mixed-content ์˜ค๋ฅ˜๋ฅผ ํ•ด๊ฒฐํ–ˆ๋‹ค.

๐Ÿ–ฑ๏ธ"Node.js๋ฅผ ํ†ตํ•ด mixed-content ์˜ค๋ฅ˜ ํ•ด๊ฒฐ" ์— ๋Œ€ํ•œ ๋ธ”๋กœ๊ทธ ๊ธ€ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ

7) Node.js ๊ณต๋ถ€

Node.js๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ์ •๋ฆฌํ•œ ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ

6)์˜ mixed-content ์˜ค๋ฅ˜๋ฅผ ๋งŒ๋‚˜๋ฉด์„œ ์–ธ์  ๊ฐ€ ํ•˜๊ฒ ์ง€๋ผ๋ฉด ์ƒ๊ฐํ•œ node.js์— ๋Œ€ํ•œ ๊ณต๋ถ€๋ฅผ ์‹œ์ž‘ํ–ˆ๋‹ค. node.js๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ์„œ๋ฒ„์—์„œ react ํ”„๋กœ์ ํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ์—ด ์ˆ˜ ์žˆ์„ ๊ฒƒ์ธ์ง€, ์„œ๋ฒ„์™€ ํ”„๋ก ํŠธ์—”ํŠธ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ์ฃผ๊ณ  ๋ฐ›์„ ์ˆ˜ ์žˆ์„์ง€, ๊ทธ ๊ณผ์ •์—์„œ ๋น„๋™๊ธฐ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•  ๊ฒƒ์ธ์ง€๋ฅผ ์ค‘์ ์ ์œผ๋กœ ๋ดค๋‹ค.

๊ทธ๋ฆฌ๊ณ  node ์„œ๋ฒ„๋ฅผ ํ†ตํ•ด react๋ฅผ ์—ด์˜€์„ ๋•Œ, css์˜ ํŒŒ์ผ์˜ ์†์„ฑ๋“ค์˜ ์ˆœ์„œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์—ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด css ์†์„ฑ์„ ์ž‘์„ฑํ•  ๋•Œ ํ•ด๋‹น ์šฐ์„ ์ˆœ์œ„๊ฐ€ ์ ์šฉ๋˜์–ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ๋ผ๋ฉด ๋’ค์— ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ํ•˜๋‚˜์˜ ์ฝ”๋“œ๋กค ๋ณด๋‹ค ์ •ํ™•ํ•˜๊ฒŒ ์„ค์ •ํ•˜๋Š” ๊ฒŒ ์ข‹๊ฒ ๋‹ค๋Š” ๊ฒฝํ—˜๋„ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์˜ค๋ฅ˜๊ฐ€ ๋‚œ scss ์ฝ”๋“œ

๋ณ€๊ฒฝ ์ „
	.bar{
   //...
   border : 7px solid $bar_bg;
   border-bottom:7px solid $bar_percent_bg;
   border-right: 7px solid $bar_percent_bg;
   
   }


border-bottom์ด border ๋ณด๋‹ค ์šฐ์„  ์ ์šฉ๋˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋Š”๋ฐ node ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๋ฉด border-bottom ํ›„์— border๊ฐ€ ๋‚˜์™€์„œ border-bottom์ด ์•„๋‹Œ border๊ฐ€ ์šฐ์„  ์‹คํ–‰๋˜๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ผ๋ชฐ ์ƒํ™ฉ์„ ํ‘œํ˜„ํ•˜๋Š” ๊ทธ๋ž˜ํ”„๊ฐ€ ์ค‘๊ฐ„์— ๋Š๊ธฐ๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋‹ค.

๋ณ€๊ฒฝ ํ›„
.bar{
      border-style:solid;
     border-width: 7px;
     border-color: $bar_bg $bar_percent_bg $bar_percent_bg $bar_bg;
     }

3. ์•„์‰ฌ์šด ์ 

์•„๋ž˜์˜ ์‚ฌ์ง„์ฒ˜๋Ÿผ, ์›๋ž˜ ๊ณ„ํšํ•œ ๋ฒ„์ „์—๋Š” ์ „๊ตญ์˜ ๋‚ ์”จ๋ฅผ ์ผ์ฃผ์ผ๋ณ„๋กœ ์•Œ๋ ค์ฃผ๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์—ˆ์ง€๋งŒ ๋ฐฐํฌ๋œ ์ตœ์ข… ๋ฒ„์ „์—๋„Œ ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์‚ญ์ œํ–ˆ๋‹ค.

์ด์œ ๋Š” ETIMEDOUT ์—๋Ÿฌ ๋•Œ๋ฌธ์ด์˜€๋‹ค.
ETIMEDOUT์€ ์—ฐ๊ฒฐ ์‹œ๊ฐ„ ์ดˆ๊ณผ์˜ค๋ฅ˜๋กœ, ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ๋Š” ์ „๊ตญ ๋‚ ์”จ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ์—ˆ์ง€๋งŒ, cloudeType์œผ๋กœ Node์™€ React๋ฅผ ๋ฐฐํฌํ•œ ๋ฐฐํฌ์‚ฌ์ดํŠธ์—์„œ๋Š” ETIMEDOUT ์˜ค๋ฅ˜๊ฐ€ ๋‚˜ํƒ€๋‚ฌ๋‹ค.

ํ•ด๋‹น ์—๋Ÿฌ์˜ ์›์ธ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. ์„œ๋ฒ„์˜ ์ •๋ณด๋ฅผ ์ž˜๋ชป ๊ธฐ์ž…
  2. ์„œ๋ฒ„์— ์ ‘๊ทผํ•  ๊ถŒํ•œ์ด ์—†์Œ
  3. ์š”์ฒญ ์‹œ๊ฐ„์ด ๋ถ€์กฑ

๊ณต๊ณต API๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๊ฒƒ์—๋Š” ๋ฌธ์ œ๊ฐ€ ์—†์—ˆ๊ณ , ์ „๊ตญ ์‹œ๋„๋ณ„ ์ผ์ฃผ์ผ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต๊ณตAPI์—์„œ ์ œ๊ณตํ•ด์ฃผ๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋“ค(ํ•˜๋Š˜์ƒํƒœ,์ดˆ๋‹จ๊ธฐ์˜ˆ๋ณด,๋‹จ๊ธฐ์˜ˆ๋ณด,์ค‘๊ธฐ์˜ˆ๋ณด๋“ฑ๋“ฑ)์„ ์‹œ๋„๋ณ„๋กœ ์ผ์ฃผ์ผ์น˜ ๋ฐ›์•„์™€์„œ ๋ฐ›์•„์˜ค๋‹ค๋ณด๋‹ˆ, ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๋Š” ์‹œ๊ฐ„์ด ํŠน์ • ์‹œ๊ฐ„์„ ๋„˜๋‹ค๋ณด๋‹ˆ "ETIMEDOUT" ์˜ค๋ฅ˜๊ฐ€ ์ƒ๊ธด๊ฒƒ ๊ฐ™๋‹ค.

๋‹ค๋ฅธ ๋ฐฐํฌ ์‚ฌ์ดํŠธ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ๋„ ์žˆ์ง€๋งŒ, ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ๋„ ์ „๊ตญ ๋‚ ์”จ๋ฅผ ๋ฐ›์•„์˜ค๋Š” ์ž‘์—…์œผ๋กœ ์ธํ•ด ๋กœ๋”ฉ ์‹œ๊ฐ„์ด 5๋ถ„์ •๋„ ๊ฑธ๋ ค์„œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ธก๋ฉด์—์„œ ์ข‹์ง€ ๋ชปํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•ด์„œ ์ „๊ตญ ๋‚ ์”จ๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ๊ธฐ๋Šฅ์€ ์ œ์™ธํ•˜๊ธฐ๋„ ํ–ˆ๋‹ค.

4. ๋งˆ๋ฌด๋ฆฌ

twitter์™€ notion์„ ํด๋ก  ์ฝ”๋”ฉํ•˜๋Š” ํ”„๋กœ์ ํŠธ๋“ค์„ ํ˜ผ์ž ์ฒ˜์Œ๋ถ€ํ„ฐ ๋ชจ๋“  ๊ฒƒ์„ ๋ถ€๋”ช์น˜๋ฉด์„œ ํ•˜๋‹ค๋ณด๋‹ˆ ๋ช‡๋‹ฌ ์”ฉ ์‹œ๊ฐ„์ด ๊ฑธ๋ ธ๊ณ  ์–ธ์ œ ์ด๊ฑธ ๋๋‚ผ ์ˆ˜ ์žˆ์„๊นŒํ•˜๋ฉฐ ๊ณ„์†๋˜๋Š” ์˜ค๋ฅ˜์™€ ์ˆ˜์ •์— ํž˜๋“ค์—ˆ์—ˆ๋‹ค. ์ด์ œ ์–ธ์–ด๋ฅผ ๋ง‰ ๋ฐฐ์šด ๋ณ‘์•„๋ฆฌ ์ˆ˜์ค€์—์„œ ๋„ˆ๋ฌด ํ˜ธ๊ธฐ๋กญ๊ฒŒ ๊ฑฐ๋Œ€ํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ดœํžˆ ์‹œ์ž‘ํ•œ ๊ฑฐ ์•„๋‹๊นŒํ•˜๋Š” ์ƒ๊ฐ๋„ ๋“ค์—ˆ๋Š”๋ฐ weather ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋ฉด์„œ ๊ทธ๋ž˜๋„ ์•ž์„  ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋ฉด์„œ ๋ฐฐ์šฐ๊ณ  ์ฒด๋“ํ•œ ๊ฒƒ๋“ค์ด ์–ด๋Š์ •๋„๋Š” ๋‚ด๊ฒƒ์ด ๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ์‹œ๊ฐ„์ด์˜€๋‹ค.

์•ž์„  ๋‘ ํ”„๋กœ์ ํŠธ์— ๋น„ํ•ด์„œ ํ”„๋กœ์ ํŠธ์˜ ๋ณต์žก๋„๋‚˜ ํฌ๊ธฐ๊ฐ€ ํฌ์ง€๋Š” ์•Š์ง€๋งŒ, ๊ทธ๋™์•ˆ ๊ถ๊ธˆํ–ˆ๊ณ  ํ•ด๊ฒฐํ•˜๊ณ  ์‹ถ์—ˆ๋˜ ์™ธ๋ถ€ API ์ด์šฉ, redux-middleware์™€ redux-toolkit์‚ฌ์šฉ, node ์„œ๋ฒ„์™€ react ์—ฐ๋™ํ•˜๊ธฐ ๋“ฑ์„ ํ•ด๋ดค๊ณ  ์—ฌ๋Ÿฌ ์˜ค๋ฅ˜๋“ค์„ ์ˆ˜์ •ํ•˜๊ณ  "๋„ค์ด๋ฒ„ ๋‚ ์”จ"์—์„œ ์‚ฌ์šฉํ•œ ์›น๋””์ž์ธ๊ณผ class, css ์š”์†Œ๋“ค์„ ๊ตฌ๊ฒฝํ•˜๋ฉด์„œ ์ƒˆ๋กœ์šด ๊ฒƒ๋“ค์„ ์•Œ์•„๊ฐ€๊ธฐ๋„ ํ•œ ๋ฟŒ๋“ฏํ•œ ๊ฒฝํ—˜์ด์˜€๋‹ค.

profile
๐Ÿฃํ”„๋ก ํŠธ ๊ฐœ๋ฐœ ๊ณต๋ถ€ ์ค‘

1๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2023๋…„ 10์›” 31์ผ

๋„ˆ๋ฌด๋„ˆ๋ฌด ์ž˜ ๋ดค์Šต๋‹ˆ๋‹ค! ์กด๊ฒฝ์‹ฌ์ด ๋“œ๋Š”๋ฐ์š”
๋‚ ์”จ ๊ณต๊ณต๋ฐ์ดํ„ฐ ์‚ฌ์šฉ๊ณผ ๊ด€๋ จํ•ด์„œ ์—ฌ์ญค๋ณด๊ณ  ์‹ถ์€๊ฒŒ ์žˆ๋Š”๋ฐ ๋”ฐ๋Ÿฌ ์—ฐ๋ฝ๋“œ๋ฆด ๋ฐฉ๋ฒ•์ด ์žˆ์„๊นŒ์š”?

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ