728x90
반응형

Internet인터넷과 프로토콜

: 인터넷은 전 세계에 걸쳐 파일 전송 등의 데이터 통신 서비스를 받을 수 있는 컴퓨터 네트워크 시스템을 의미한다. 그래서 만약 우리가 ‘인터넷을 사용한다’ 라고 말하는 것은 사업자가 만들어 놓은 네트워크 인프라를 사용하는 것이다. (여기서 사업자는 SK,KT 회사들을 의미한다.)

인터넷 망을 통해 데이터를 디지털 신호로 바꾸어 전달하고, 받은 디지털 신호를 데이터로 다시 바꾸면서 네트워크 통신을 하는 것이다. 이 때, 네트워크 통신을 위한 규칙, 즉 공통된 메뉴얼을 프로토콜이라고 한다.

network edge

network edge는 hosts들로 구성된다. hosts들은 사용자 또는 서버를 포함하고 기본적으로 단말들을 의미한다. server는 data center이다. 우리가 브라우저에 접속한다고 생각하면 서버에 데이터를 요청해서 데이터를 받아온다. 이렇게 끝단에 있는 것들을 network edge들이라고 생각하면 된다. 이 사이사이를 link들이 이어주는데, 이제 어떻게 보낼것인지 어디로 보낼것인지 이런것들을 처리해주는 부분이 network core부분이다.

network core

data를 보낼 때, data자체는 크기가 크니까 잘게 쪼개서 보낸다. 이 때 잘게 쪼갠 조각 하나하나를 패킷(packet)이라고 한다. 이 패킷들을 어떻게 전달할 것인지가 문제이다. network core들은 ISP쪽을 의미하고 core에 edge들이 달려있는것이다. ISP도 서로서로 연결이 되어있다. 즉, network core들은 router들의 interconnection이다.

network core의 2가지 key function은 무엇일까?

  1. Forwarding(switching) : local action이다. 라우터에서 패킷을 받았는데, 패킷에는 목적지 주소가 써져 있다. 이 목적지로 가는 패킷은 몇 번 링크로 forwarding을 해야 하는지 forwarding table을 가지고 있다. 그래서 궁극적으로 패킷이 목적지에 도달할 수 있도록 하는 것이 forwarding이다.
  2. Routing : routing은 global action이다. source에서 destination까지 어떤 경로를 취해야 하는지 패킷이 어떤 경로를 따라가야 하는지 체크하는 것이 라우팅이 된다. (라우팅 알고리즘도 여러 가지가 있다.)

packet-switching의 개념이 중요한데, 패킷 하나가 보내지면 store and forward라는 방식을 사용한다. 이 방식은 전체가 받아질때까지 버퍼에 store하고 다 받아진 다음에야 보낸다. 중요한 성능 측정중 하나가 packet transmission delay이다. host가 data packet을 보내는 경우를 생각해보자. packet의 길이가 Lbits라고 한고, 전송속도를 R이라고 가정하자. 그럼 packet transmission delay, 즉 L bit packet을 link로 보내는데 걸리는 시간은 L/R이 된다.

그런데 packet switching은 queueing이 발생할 수 밖에 없다. 왜냐하면 link를 공유하게 되는데 전송되어 빼내는 속도보다 들어오는 속도가 빠르면 congestion, 즉 queueing delay가 생긴다.

그럼 잃어버리는 data도 생길것이다. arrival rate > transmission rate일 때, 패킷이 버퍼에 쌓이는데 메모리 용량은 한계가 있다. 그럼 packet drop이 생긴다.

여기서 packet switching과 반대되는 개념인 circuit switching도 있다. end - end resource가 예약이 되는 개념이다. 그러니까 이 link를 독점적으로 사용하는 것이다. traffic이 없다고 해도 항상 비워져 있어야한다. 그래서 telephone network에서 많이 쓴다. 전화하는 중간에 전화해도 통화중이라고 알려주지 같이 통화할 수 있는건 아닌것처럼 말이다.

그럼 packet switching과 circuit switcing을 비교해보자. 만약에 link로 1Gb/s통과하고 각각의 사용자는 사용하면 100 Mb/s를 사용하고 10%확률로 사용한다고 가정하자. 그럼 circuit switching은 1Gb에서 100Mb를 나눈값이 10명밖에 사용을 못한다. 그런데 packet switching은 35명이 사용한다고 가정할 때 35명이 동시에 사용할 확률이 0.004밖에 안된다. 그래서 여러명이서도 사용할 수 있는 것이다.

 

그럼 packet switching이 무조건 우세일까? 그건 아니다. 성능을 보장해야 하는 경우는 circuit switching을 사용하게 된다.

pacekt switcing은 data loss가 있으므로 reliabel data transfer이나 congestion control이 필요하다.

 

Performance : loss, delay, throughput

인터넷은 기본적으로 packet switching이다. packet switching은 모든 resource를 공유하는 것이라고 위에서 설명하였다.

delay에는 4개의 요소가 있는데, 4가지 요소는 transmission delay, nodal processing delay, queueing delay, propagation delay이다.

nodal processing delay는 packet이 도착했을 때, packet에 bit error가 있는지 없는지 확인하고 output link를 결정하는 과정이다. nodal processing delay는 보통 microsec로 굉장히 짧다. 그래서 보통 하드웨어에서 구현이 되어 있다.

queuing delay : transmission 받으려고 기다리는 delay이다. router의 congestion level에 따라서 변화가 굉장히 심하게 있을 수 있다. 어떤 경우에는 queueing delay가 0이었다가 어떤 경우에는 굉장히 컸다가 굉장히 dynamic한 상황이다.( nodal processing delay같은 경우는 거의 변화가 없다. ) Queueing delay는 변화가 있기 때문에 여러가지 요소가 있다.

transmission delay : packet을 링크에 밀어넣는 속도이고, packet length가 Lbits이고, transmission rate이 R bps다 라고 하면 L/R초가 걸리게 되는 것이다.

propagation delay : bit는 전기신호인데 001 이런식으로 보내는데 전자기파, 빛의 속도 20만킬로미터 퍼 second가 되는데 physical link가 d미터였다고 하면 d/s가 걸리는 것이다. link가 아주 길지 아니면 굉장히 delay가 짧다.

이것들이 다 합쳐서 Packet delay가 된다.

 

packet arrival rate이 a라고 해보자. packet per second. packet length는 bits이다. a packet per second인데 하나의 packet이 Lbits니까 arrival rate이 aL bps가 된다. R은 transmission rate이다.

 

La/R → traffic intensity

그럼 여기서 의문이 생긴다. traffic intensity가 1보다 작다는 것은 queueing delay가 발생안한다는 뜻이 아닐까?

아니다. 여기서 arrival rate는 평균을 의미하기 때문에 이 경우에도 queueing delay가 발생할 수 있다.

 

Packet loss

congestion이 발생할때 버퍼링이 될 수 있는데 버퍼가 꽉찬 상태에서 추가로 들어오면은 loss가 발생한다. loss packet이 발생하면 재전송 mechanism이 있다. 그리고 throughput은 얼마나 통과했느냐 얼마나 data들이 통과했는냐를 나타내는것이 throughput이 된다. sender에서 receiver까지 bit가 전송되는 속도. instantaneous는 어떤 시점에서의 속도를 나타내는 것이다.

average는 긴 시간 간격이고 그 시간안에 전송된것.

Rs가 Rc보다 작은 상황이라고 해보자. Rs속도가 안나오면 Rc가 아무리 높아도 사용안된다.

bottleneck problem. → 그럼 end to end 는 최대 Rs가 된다. (병목현상이라고 부른다.)

즉, 여러 곳에서 들어올 때 최소값이 end-end throughput이 되는 것이다.

 

TCP / IP 계층

Application Layer : 특정 서비스를 제공하기 위해 애플리케이션끼리 정보를 주고받을 수 있게 해준다. ( FTP, HTTP, SSH, Telnet, DNS, SMTP)

—> ex> 브라우저와 웹서버가 HTTP요청, 응답을 통해 통신하는 것

Transport Layer : 송신된 데이터를 수신측 애플리케이션에 확실하게 전달해 준다. 네트워크 통신을 하는 애플리케이션은 포트 번호를 사용하게 된다. Transport Layer는 port 번호를 사용해서 애플리케이션을 찾아주는 역할을 한다. TCP, UDP와 같은 프로토콜이 사용된다.

Internet Layer : 수신측까지 데이터를 전달하기 위해 사용된다. 송신측, 수신측 모두 IP 주소를 가지고 있다. IP주소를 바탕으로 정확한 목적지를 찾아갈 수 있게 해준다. IP, ARP같은 프로토콜이 사용된다.

Network Access Layer : 네트워크에 직접 연결된 기기 간의 데이터 전송을 도와준다. 여기서는 물리적 주소인 MAC주소를 사용한다. Ethernet, PPP, Token Ring과 같은 프로토콜을 사용한다.

 

그럼 우리가 www.google.com을 웹브라우저에 입력하면 무슨 일이 일어날까?

먼저 www.google.com을을 입력하면 google서버의 80포트로 HTTP Request를 보내는 것을 의미한다. 해당 요청을 인터넷을 통해 구글서버로 전달하기 위해 우리는 패킷을 만들어야 한다. 패킷에는 각 계층에 필요한 정보들이 담겨야 한다.

( 계층별로 HTTP, TCP, IP, Ethernet 프로토콜을 사용한다고 생각하자.)

 

Application Layer에는 Http Request가 들어간다.

Transport Layer에는 SP, DP. SP는 시작포트, DP는 도착포트. 시작포트번호는 내 컴퓨터에서 만든 소켓의 포트 번호라서 당연히 컴퓨터는 알고 있을 것이다. 목적지 포트 번호 또한 80으로 알고 있다.

(80은 웹서버의 well known 포트 번호이다)

 

IP header에는 SA와 DA. 시작 IP주소, 도착 IP주소. 시작 IP주소는 알고 있겠지만,(내pc니까) 목적지 ip주소는 모른다. www.google.com이라는 도메인 주소만 알고 있기때문에 DNS 프로토콜을 사용해서 IP주소를 알 수 있다.

브라우저는 OS에게 도메인에 대한 IP주소를 알고 싶다고 요청한다. 그럼 OS에서 DNS서버로 요청을 보내게 된다. 그럼 OS가 DNS 서버를 어떻게 알고 있을까? DNS서버 주소는 이미 컴퓨터에 등록이 되어 있다. 

 

DNS또한 HTTP와 같은 애플리케이션 계층 프로토콜이다. 53번 포트를 사용한다. DNS도 HTTP Request와 비슷하게 도메인이 담긴 쿼리를 도메인 서버로 보낸다. 그럼 도메인 서버가 IP주소를 응답 해준다.

DNS는 Transport Layer에서 UDP라는 프로토콜을 사용한다. UDP는 TCP와는 다르게 헤더가 간단하다. 포트번호말고 다른게 없다. 그 이유는 UDP가 비연결지향형 프로토콜이기 때문이다.

 

Ethernet 프로토콜에 대한 헤더를 알아야하는데 아직 MAC주소를 모른다. MAC주소는 어떻게 알아와야할까? 이전까지는 목표인 구글 서버에 대한 정보가 필요했다. 그럼 MAC주소도 우리의 목적지인 구글 웹서버의 MAC주소가 필요할까? 아니다. 여기서필요한  MAC주소는 구글의 MAC주소 대신 물리적으로 연결된 우리집 공유기의 MAC주소가 필요하다. 이 공유기를 통해 다른 네트워크와 연결이 가능하니까 게이트웨이라고 부르기도 한다. 게이트웨이의 IP는 이미 알고 있다. ( netstat -rn 명령어를 통해 알 수 있음)

 

그럼 어떻게 IP주소로 MAC주소를 알 수 있을까? IP주소로 MAC주소를 알아내기 위해서 ARP 프로토콜을 사용한다. ARP 프로토콜을 IP주소를 MAC주소로 바꾸어주는 주소해석 프로토콜이다.

 

TCP 프로토콜은 데이터를 송신하기 전에 송신측과 수신측이 서로 연결되는 작업이 필요하다. 이러한 작업을 3 Way Handshaking이라고 부른다.

3way handshaking을 수행하기 위해서는 TCP header에 표시한 플래그들이 사용된다. 이러한 플래그들을 컨트롤 비트라고 부른다. SYN과 ACK 플래그가 사용된다.

클라이언트는 서버에게 접속을 요청하는 SYN 패킷을 보낸다. 서버는 SYN 요청을 받고, 클라이언트에게 요청을 수락한다는 ACK와 SYN플래그가 설정된 패킷을 보낸다.

클라이언트는 서버에게 다시 ACK를 보낸다. 이제부터 연결이 이루어지고 데이터가 오가게 된다. 이런 3가지 과정을 3way handshaking이라고 한다. 

 

내가 사용하는 컴퓨터는 Private IP를 사용하고 있다. Private IP는 외부의 네트워크 환경에서 IP주소를 찾지 못한다. 그래서 공유기를 통해 나갈 때 public IP로 주소를 변환하여 나가는 작업이 필요하다.

이러한 작업을 NAT (Network Address Translation)이라고 한다.

우리집 공유기를 거치고 나서 구글 서버에 도착하기 위해서는 여러 라우터를 거쳐야 한다. 라우터는 네트워크와 네트워크를 연결해주는 역할을 한다. 라우터가 목적이 경로를 찾아 나가는 과정을 라우팅이라고 한다. 라우팅을 거쳐 구글 서버가 연결된 라우터까지 데이터가 도착을 하면, 패킷의 IP헤더에 기록된 구글 서버 IP주소를 통해 MAC주소를 얻어와야 한다. 이때 이전에 설명했던 ARP프로토콜을 사용한다.

이때 ARP는 라우터가 연결된 네트워크에 브로드캐스팅된다. 목적지 구글서버가 자신의 IP로 온 ARP 요청을 받고 MAC주소를 응답해준다. 이제 목적지 구글서버의 MAC주소를 알았으니 데이터가 물리적으로 전달될 수 있다. ARP로 IP주소를 통해 MAC주소를 얻고, 드디어 목적지 구글 서버에 데이터가 도착한다.

그럼 구글 서버에서 HTTP Request를 받고 응답을 돌려보낸다. '/'에 매핑된 GET요청을 처리해서 적절한 HTML을 응답해준다. 그리고, HTTP 요청과 응답과정이 끝나면 연결을 종료해야 한다. 여기서도 TCP 컨트롤 비트가 사용된다. 이 단계에서는 ACK, FIN플래그가 사용된다.

클라이언트가 서버로 연결을 종료하겠다는 FIN 플래그를 전송한다. 서버는 클라이언트에게 ACK 메시지를 보내고 자신의 통신이 끝날 때까지 기다린다. 서버가 통신이 끝나면 클라이언트로 FIN을 보낸다. 클라이언트는 확인했다는 의미로 서버에게 ACK를 보낸다. 그럼 연결종료가 완료된다. 총 4단계이고 이걸 4way handshaking이라고 한다.

여기서 서버가 FIN을 보내는 과정에서 한가지 문제가 발생할 수 있다. 서버가 FIN을 보내기 전에 보냈던 데이터가 FIN 보다 늦게 도착할 경우이다. 서버로부터 FIN을 수신했다고 클라이언트가 바로 연결된 소켓을 닫아버리면 FIN을 보내기 전에 보낸 패킷은 영영 클라이언트가 받을 수 없게 된다. 그래서 클라이언트는 서버로부터 FIN요청을 받더라도 일정시간동안 소켓을 닫지 않고, 혹시나 아직 도착하지 않은 잉여 패킷을 기다린다. 이렇게 4way handshaking이 완료되어도, 소켓을 닫지않고 잉여패킷을 기다리는 상태를 TIME_WAIT이라고 한다.

 

728x90
반응형

'백엔드 > Network Programming' 카테고리의 다른 글

Non-blocking I/O in Java  (0) 2022.12.12
Socket Programming(3) - Socket for Server  (0) 2022.11.21
Socket Programming(2) - Socket for Client  (0) 2022.11.13
Socket Programming(1) - socket이란?  (0) 2022.11.13
URL and URI's key  (2) 2022.10.15
728x90
반응형

Next.js를 사용할 때 생겼던 Hydration 이슈이다. Hydration이란 Next.js의 SSR 특성 때문에 생기는 오류이다.

Next.js의 SSR 특성은 정적 HTML 파일을 먼저 서버에서 가지고 와서 브라우저에서 js파일로 다시 랜더링해주는 특성이다. 이 특성으로 인하여 사용자는 더 빠르게 화면을 볼 수 있다.

Hydration error는 SSR에서 생기는 부작용인데 SSR또는 SSG에 의하여 pre-render되는 React tree와 브라우저에서 render되는 React tree가 달라서 발생하는 문제이다.

SSR에서 render되는 것을 Hydration이라고 하기 때문에 Hydration Error라고 한다.

 

해결방안 1

보통 이런 이슈는 특정 라이브러리나 application code에서 pre-render되는 것과 browser에서 render되는것과의 차이에 의해서 생긴다.

예를 들어 ‘window’라는 component가 rendering되는 것을 보자.

function MyComponent() {
  // This condition depends on `window`. During the first render of the browser the `color` variable will be different
  const color = typeof window !== 'undefined' ? 'red' : 'blue'
  // As color is passed as a prop there is a mismatch between what was rendered server-side vs what was rendered in the first render
  return <h1 className={`title ${color}`}>Hello World!</h1>
}

이 코드는 React의 Hook중에서 useEffect를 사용하여 아래와 같이 수정할 수 있다.

// In order to prevent the first render from being different you can use `useEffect` which is only executed in the browser and is executed during hydration
import { useEffect, useState } from 'react'
function MyComponent() {
  // The default value is 'blue', it will be used during pre-rendering and the first render in the browser (hydration)
  const [color, setColor] = useState('blue')
  // During hydration `useEffect` is called. `window` is available in `useEffect`. In this case because we know we're in the browser checking for window is not needed. If you need to read something from window that is fine.
  // By calling `setColor` in `useEffect` a render is triggered after hydrating, this causes the "browser specific" value to be available. In this case 'red'.
  useEffect(() => setColor('red'), [])
  // As color is a state passed as a prop there is no mismatch between what was rendered server-side vs what was rendered in the first render. After useEffect runs the color is set to 'red'
  return <h1 className={`title ${color}`}>Hello World!</h1>
}

 

또한 css-in-js 라이브러리를 사용할 경우에의 문제일 수도 있다.

css in js 라이브러리의 종류에는 Styled-Components나 Emotion 같은 것이 있는데, css in js l라이브러리가 pre-render되지 않는다면 hydration mismatch로 이어질 수 있다.

 

나의 경우

나의 경우 Cookie를 사용하는 Website를 만들고 있었는데 처음에 User의 정보가 Cookie에 있으면 로그인을 유저의 이름으로 바꾸어줄려고 했다.

이 코드를 컴포넌트 내에서 사용할 때 useEffect로 Cookies.get 함수를 넣어서 바꾸어주어 해결하였다.

const [logined, setLogined] = useState(false);
  useEffect(() => {
    if (
      Cookies.get("userInfo") !== "null" ||
      Cookies.get("userInfo" !== "undefined")
    ) {
      setLogined(true);
    } else {
      setLogined(false);
    }
  });

 

해결 방안 2

pages/_document가 없거나 Babel plugin이 없다면 추가하여서 사용하도록 하자.

// .babelrc를 만들어서 아래 코드를 추가해주자
{
    "presets": [
        "next/babel"
    ],
    "plugins": [
        "babel-plugin-styled-components"
    ]
}

 

그 외의 이슈는 아래 공식문서를 참고하면 될 것 같다.

https://nextjs.org/docs/messages/react-hydration-error

 

react-hydration-error | Next.js

React Hydration Error While rendering your application, there was a difference between the React tree that was pre-rendered (SSR/SSG) and the React tree that rendered during the first render in the Browser. The first render is called Hydration which is a f

nextjs.org

 

728x90
반응형
728x90
반응형

https://reactrouter.com/docs/en/v6/upgrading/v5#upgrading-from-v5

 

React Router | Upgrading from v5

Declarative routing for React apps at any scale

reactrouter.com

v5에서 v6가 되면서 react router dom 모듈에서 변경된 점이 몇 개 있다.

1. <Switch> -> <Routes>

젤 먼저 Switch가 없어지고 Routes로 바뀌었다.

원래 Switch로 바깥을 둘러싸고 그 안에 Route를 사용하여서 이동하였으나, Switch 대신 Routes를 쓰면된다.

 

2. Route 변경

Route의 속성 중에서 component는 element로 바뀌었다.

 

3. 중첩 라우팅

: Router안에 Route를 넣어서 구현할 수 있다. 아래에 있는 변환과정을 통해 설명하겠다.

 

4. Outlet ->nested routes 구현할 때

: 만약 상위의 컴포넌트를 레이아웃화하고 싶을 때, Outlet을 사용한다. 

Outlet을 사용하면 {children}으로 사용하는것과 같은 효과가 난다.

 

5. useRouteMatch : 특정한 URL에 있는지의 여부를 알려준다.

react-router-dom v6에는 v5에 있는 useRouteMatch hook의 대체제로 useMatch()를 사용한다. 

https://ui.dev/react-router-nested-routes

 

The Guide to Nested Routes with React Router

In this comprehensive, up-to-date guide, you'll learn everything you need to know about creating nested routes with React Router.

ui.dev

 

v5에서 v6로 변환과정 예시

<Switch>
    <Route path={`/${coinId}/price`}>
      <Price />
    </Route>
    <Route path={`/${coinId}/chart`}>
      <Chart />
    </Route>
 </Switch>

위와 같은 코드를 v6 버젼으로 변경해보자.(3번을 자세하게 해보겠다.)

v6에서 nested routes를 구현하는 방법은 두 가지가 있다.

첫번째는 부모 route의 path 마지막에 /*를 적어 이 route의 내부에서 nested route가 render된다는 것을 표시하고 자식 route를 부모 route의 element 내부에 작성한다.

(상위 Route에 /*를 적어두면 된다. 여기서는 /:coinId/* 라고 Router.tsx에 기입하였다.)

<Routes>
    <Route path="price" element={<Price />}></Route>
    <Route path="chart" element={<Chart />}></Route>
</Routes>

두번째 방법은 Routes가 상대경로도 지원하기 때문에 path="chart"와 같이 써도 작동한다. 이 코드를 상위 컴포넌트에서 작성해주면 된다.

<Route path="/:coinId" element={<Coin />}}
    <Route path="chart" element={<Chart />} />
    <Route path="price" element={<Price />} />
</Route>
728x90
반응형
728x90
반응형

제목 그대로 새로고침하면 스타일이 적용안되는 문제가 생겼다.

Next.js가 서버 사이드 렌더링이라 나는 material ui를 사용하고 있었는데 스타일이 정상적으로 먹히지 않았다.

그렇기 때문에 몇가지 작업을 해줘야 하는데 일반적인 Server Rendering인 경우에는 Material Server Rendering을 참고하면 되고, Next.js를 사용하면 Material with Next.js를 참고하면 된다.
(https://github.com/mui/material-ui/tree/master/examples/nextjs)

 

내가 해결한 해결방법 코드

_app.js

import { useEffect } from "react";
import "../styles/globals.css";
import { DarkModeProvider } from "../utils/DarkModeProvider";

function MyApp({ Component, pageProps }) {
  useEffect(() => {
    const jssStyles = document.querySelector("#jss-server-side");
    if (jssStyles) {
      jssStyles.parentElement.removeChild(jssStyles);
    }
  }, []);
  return (
    <DarkModeProvider>
      <Component {...pageProps} />
    </DarkModeProvider>
  );
}

export default MyApp;

_document.js

import { ServerStyleSheets } from "@material-ui/core/styles";
import Document, { Head, Html, Main, NextScript } from "next/document";
import React from "react";

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head>
          <link
            rel="stylesheet"
            href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

MyDocument.getInitialProps = async (ctx) => {
  const sheets = new ServerStyleSheets();
  const originalRenderPage = ctx.renderPage;
  ctx.renderPage = () => {
    return originalRenderPage({
      enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
    });
  };
  const initialProps = await Document.getInitialProps(ctx);
  return {
    ...initialProps,
    styles: [
      ...React.Children.toArray(initialProps.styles),
      sheets.getStyleElement(),
    ],
  };
};
728x90
반응형

'프론트엔드 > Next.js' 카테고리의 다른 글

Hydration Error  (0) 2022.08.24
Next.js로 프로젝트 시작  (0) 2022.08.04
Next.js 설치와 폴더 구조  (0) 2022.08.04
Next.js 를 사용하는 이유  (0) 2022.08.04
728x90
반응형

제가 하고 있는 프로젝트를 기록합니다..

폴더 구조는 먼저 components, pages, public, styles, utils를 사용하였습니다. (추후에 더 추가할 예정입니다)

사용한 툴 : Material UI , next.js , styled-components

src

  • components
    • Layout.js (전체적인 레이아웃)
  • pages
    • _app.js
    • _document.js
    • index.js : 메인화면 
  • utils
    • data.js : 전체적인 data (백엔드와 연동할 때 여기로 데이터 받아오기)
    • DarkModeProvider.js : 다크모드 설정
    • styles.js : 여기서 스타일링 함
  • styles

_app.js에서는 refresh하면 스타일이 아예 적용안되는 문제(서버 사이드 렌더링에서의 문제)를 해결하였습니다. (이 내용은 따로 포스팅 하였습니다) 그리고 다크 모드를 적용하고 싶어서 utils 폴더에 DarkModeProvider를 만들어주었습니다.

DarkModeProvider로 전체를 감싸주었습니다.

import { useEffect } from "react";
import "../styles/globals.css";
import { DarkModeProvider } from "../utils/DarkModeProvider";

function MyApp({ Component, pageProps }) {
  useEffect(() => {
    const jssStyles = document.querySelector("#jss-server-side");
    if (jssStyles) {
      jssStyles.parentElement.removeChild(jssStyles);
    }
  }, []);
  return (
    <DarkModeProvider>
      <Component {...pageProps} />
    </DarkModeProvider>
  );
}

export default MyApp;

 

_document.js에서는 link로 글꼴을 받아왔고 여기서도 물론 새로고침하면 스타일이 자꾸 돌아가는 문제를 해결하였습니다.

import { ServerStyleSheets } from "@material-ui/core/styles";
import Document, { Head, Html, Main, NextScript } from "next/document";
import React from "react";

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head>
          <link
            rel="stylesheet"
            href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

MyDocument.getInitialProps = async (ctx) => {
  const sheets = new ServerStyleSheets();
  const originalRenderPage = ctx.renderPage;
  ctx.renderPage = () => {
    return originalRenderPage({
      enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
    });
  };
  const initialProps = await Document.getInitialProps(ctx);
  return {
    ...initialProps,
    styles: [
      ...React.Children.toArray(initialProps.styles),
      sheets.getStyleElement(),
    ],
  };
};

 

728x90
반응형
728x90
반응형

Next.js는 React를 CRA(Create React App)으로 설치해보았으면 다음 명령어 그대로 사용하면 된다. 

npx create-next-app

 

 

다음과 같이 설치하여 npm run dev 또는 yarn dev를 통해 실행하면 된다.

폴더 구조

폴더 구조는 _app.js 와 index.js가 만들어져 있을 것이고, api폴더는 api를 사용할 때, public은 보통 image를 넣어 사용한다. 

React를 설치하였을 때와 다른 _app.js와 _document.js 같은 것들에 대해서 알아보겠다.

 

먼저 _app은 서버로 요청이 들어왔을 때 가장 먼저 실행되는 컴포넌트로, 페이지에 적용할 공통 레이아웃의 역할을 한다.

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

여기서 pageProps는 초기 속성값이 되는데, 초기 속성값은 getInitialProps, getStaticProps, getServerSideProps 중 하나를 통해 패칭한 값이다. 

getInitialProps를 사용해 모든 페이지에서 사용할 공통 속성 값을 지정하는데 이럴 경우 자동 정적 최적화(Automatic Static Optimization)이 비활성화되어 모든 페이지가 서버 사이드 렌더링을 통해 제공된다. 

_app에서 getInitialProps를 사용하고자 한다면 App 객체를 불러온 후 getInitialProps를 통해 데이터를 불러와야 한다.

app.getInitialProps = async(appContext) => {
	const appProps = await App.getInitialProps(appContext);

	return {...appProps}
}

 

그 다음은 document 페이지이다.

create-next-app으로 설치하면 document는 없을 것이다. 하지만 보통 <head>태그나 <body>태그 안에 들어갈 내용을 커스텀할 때 활용하기 위하여 만들어준다. 

_document.js 로 만들어준다. 이건 _app 다음에 실행된다.폰트 import나 charset, 웹 접근성 관련 태그를 여기서 설정한다. 

_document를 작성할 때는 Document 클래스를 상속받는 클래스 컴포넌트로 작성해야만 하며, 렌더 함수는 꼭 <Html>, <Head>, <Main>, <NextScript> 요소를 리턴해 주어야 한다. 

_document 에서 사용하는 <Head> 태그는 next/head가 아닌 next/document 모듈에서 불러와야 한다.

_document의 <Head>태그에는 모든 문서에 공통적으로 적용될 내용(charset, 메타태그 등)이 들어가야 한다.

_document는 언제나 서버에서 실행되므로 브라우저 api 또는 이벤트 핸들러가 포함된 코드는 실행되지 않는다.

<Main /> 부분을 제외한 부분은 브라우저에서 실행되지 않는다.

import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

즉, Next.js 의 실행되는 순서는 다음과 같다.

  1. Next Server가 GET 요청을 받는다.
  2. 요청에 맞는 Page를 찾는다.
  3. _app.js의 getInitialProps가 있다면 실행한다.
  4. Page Component의 getInitialProps가 있다면 실행한다. pageProps들을 받아온다.
  5. _document.js의 getInitialProps가 있다면 실행한다. pageProps들을 받아온다.
  6. 모든 props들을 구성하고, _app.js > page Component 순서로 rendering.
  7. 모든 Content를 구성하고 _document.js를 실행하여 html 형태로 출력한다.

Server Side

최초에 Next 서버로 요청이 들어왔을 때, Next 서버에서는 요청이 들어온 페이지에 들어갈 데이터를 Fetch하고 Html을 구성하여 Client로 보내준다.

_app.js 와 _document.js

최초로 실행된다. 없어도 된다 사실.

server only file이다. 즉, Next Server logic에 사용되는 파일이라는 뜻으로 client에서 사용되는 로직(eventListener 등의 window / DOM 로직)을 사용하면 안된다. 서버사이드 렌더링이기 때문! (앞 글에서 언급하였다)

window is not defined //이런 에러일 때 확인해보자.

최초 실행은 _app.js

_app.js는 client에서 띄우길 바라는 전체 컴포넌트의 레이아웃으로 이해하면 쉽다.

공통 레이아웃이므로 최초에 실행되어 내부에 들어갈 컴포넌트들을 실행한다.

내부에 Content들이 있다면 전부 실행하고 Html의 Body로 구성한다.

props로 받은 Component는 요청한 페이지이다. GET / 요청을 보냈다면, Component에서는 /pages/index.js 파일이 props로 내려오게 된다.

pageProps는 페이지 getInitialProps를 통해 내려 받은 props 들을 말한다.

그 다음 _document.js가 실행된다. _document.js는 static html를 구성하기 위한 _app.js에서 구성한 Html body가 어떤 형태로 들어갈지 구성하는 곳이다.

Content들을 브라우저가 html로 이해하도록 구조화 시켜주는 곳이라고 이해하면 쉽다.

_document.js에 어플리케이션 로직을 넣지말자!!

브라우저는 Main을 제외한 다른 component들을 initialize하지 않는다. 공통된 어플리케이션 로직이 필요하다면 _app.js를 활용

getInitialProps

웹 페이지는 각 페이지마다 사전에 불러와야할 데이터들이 있다.

Data Fetching이라고도 하는 로직은 CSR에서는 componentDidMount나 useEffect로 컴포넌트가 마운트 되고 나서 하는 경우가 많다.

이 과정을 서버에서 미리 처리하도록 도와주는 것이 getInitialProps이다.

데이터 패칭을 서버에서 하게 되면, 속도가 빨라진다.

Initial한 데이터가 들어오는 과정을 전제로 코드를 작성할 수 있다.

전체 페이지에 동일한 Data Fetching을 할 것인지를 정해야 한다.

공통된 Data Fetching이 필요하다면 → _app.js에 getInitialProps를 붙이면 된다.

페이지마다 다른 Data가 필요하다면 페이지마다 getInitialProps를 붙이면 된다.

import axios from 'axios';

const Page = ({ stars }) => {
  
  return <div>Next stars: {stars}</div>;
};

Page.getInitialProps = async ctx => {
  const { data } = await axios.get('...url');

  return { stars: data };
}

export default Page;

사용 시 주의할 점

  • getInitialProps 내부 로직은 서버에서 실행된다→ 따라서 Client에서만 가능한 로직은 피해야 한다.
  • 한 페이지를 로드할 때, 하나의 getInitialProps 로직만 실행된다. 예를 들어, _app.js에 getInitialProps를 달아서 사용한다면 그 하부 페이지의 getInitialProps는 실행되지 않는다. 다만 최종 결과를 pageProps에 담아서 활용하면 된다.
export default class MyApp extends App {

	static async getInitialProps({ Component, ctx }) {
		let pageProps = {};
    
    // 실행하고자 하는 component에 getInitialprops가 있으면 실행하여 props를 받아올 수 있다.
		if (Component.getInitialProps) {
			pageProps = await Component.getInitialProps(ctx);
		}

		return {
			pageProps
		};
	}

	render() {
		const { Component, pageProps, router } = this.props;
    
		return (
			<div>
				<Component {...pageProps} />
			</div>

		);
	}
};

getInitialProps는 기본적으로 받는 props가 있다. 이를 content(ctx)라고 한다.

ctx Object의 기본 구성은 다음과 같다.

  • pathname : 현재 pathname (/user?type=normal page 접속 시에는 /user)
  • query : 현재 query를 object 형태로 출력
  • asPath : 전체 path
  • req : HTTP request object
  • res : HTTP response object
  • err : Error object if any error is encountered during the rendering
728x90
반응형
728x90
반응형

웹 응용 프로그램의 구성 요소

  • User Interface - 사용자가 응용프로그램을 사용하고 상호 작용하는 방식을 설명.
  • Routing - 사용자가 응용프로그램의 다른 부분을 navigate하는 방법.
  • Data Fetching - 데이터 저장 위치 및 데이터 입수 방법.
  • Rendering - 정적 또는 동적 콘텐츠를 렌더링할 순간과 위치.
  • Integrations - 사용하는 타사 서비스(CMS, auth, payments 등) 및 이러한 서비스에 연결하는 방법.
  • Infrastructure - 응용 프로그램 코드(Serverless, CDN, Edge 등)를 배포, 저장 및 실행할 수 있는 위치.
  • Performance - 최종 사용자를 위해 애플리케이션을 최적화하는 방법.
  • Scalability -  team, data 및 트래픽 증가에 따라 애플리케이션이 어떻게 적응하는지.
  • Developer Experience -  애플리케이션 구축 및 유지 관리 경험

Next.js는 React Framework로 빠르게 web application을 만들 수 있다.

 

Next.js를 사용하는 이유는 SSR(서버 사이드 렌더링)이기 때문이다.

 서버 사이드 렌더링이란 서버에서 페이지를 그려서 클라이언트로 보낸 후 화면에 표시하는 기법이란 뜻이다. 즉, 클라이언트가 그리는 것보다 빠르다는 점이 중요하다.

 또한 검색 엔진 최적화와 빠른 페이지 렌더링이 된다.

 검색 엔진 최적화(SEO)란 구글, 네이버와 같은 검색 사이트에서 검색했을 때 결과가 사용자에게 많이 노출될 수 있도록 최적화 하는 기법이다. (물론 구글 크롤러 봇은 자바스크립트를 실행할 수 있어서 CSR의 크롤링도 가능하지만 완벽하지는 않다)

 그리고, SNS에서 링크를 공유했을 때 해당 웹 사이트의 정보를 이미지와 설명으로 표시해주는 OG Tag를 페이지 별로 적용하기 위해서는 서버 사이드 렌더링이 효율적이다.

 사용자 입장에서는 화면에 유의미한 정보가 표시되는 시간이 빨라지는 것이다.

 

서버 사이드 렌더링의 단점이라고 할 것은 Node.js 웹 애플리케이션 실행 방법을 알아야하고 서버쪽 환경 구성과 함께 클라이언트, 서버 빌드에 대한 이해가 필요하고 Node.js 환경에서 실행되기 때문에 브라우저 관련 API를 다룰 때 주의해야 한다. (이 점은 폴더 구조를 설명하면서 설명하겠다. 이 점 때문에 window나 document와 같은 브라우저 객체에 접근할 수 없다.)

 

이런 단점들을 제쳐두고 장점이 좋기 때문에 Next.js를 사용하는 것 같다. 

728x90
반응형
728x90
반응형

React에서 비동기를 쉽게 다루게 해주는 라이브러리이다. 데이터를 서버에서 가져오고 그 데이터를 모든 컴포넌트에서 사용 가능하게 캐싱하거나, 데이터 패칭 등을 지원한다. 

 

캐싱 : 예를들어 /user/2에 10초 전에 요청을 한 적이 있다면 그 때 불러온 데이터를 보여줄 수 있다. (이전에 loading을 해서 처리했던 부분이 더 빨리 진행되어 안보인다!)

편리한 기능 : data, isLoading, isSuccess, isError 등등을 사용할 수 있다.

  • 전역 상태를 건드리지 않고도 캐시하고 업데이트 할 수 있다.
  • React Query에 데이터를 가져올 위치와 데이터가 얼마나 필요한지 알려주면 즉시 캐싱, 백그라운드 업데이트 및 오래된 데이터를 처리한다.
  • 설치 : npm install react-query -> npm install @tanstack/react-query
    • react-query가 버전이 바뀌어서 @tanstack/react-query로 해야 오류가 나지 않는다. 사용법은 같다

 

React Query를 사용하기 위해서는 우선 사용하고자 하는 컴포넌트를  <QueryClientProvider> 컴포넌트로 감싸주고 provider를 만들어주어야 한다. client props는 필수이다.

QueryClientProvider 안에 있는 모든 것은 queryClient에 접근할 수 있다.

import {QueryClient,QueryClientProvider} from '@tanstack/react-query';
...
const queryClient = new QueryClient()

<QueryClientProvider client={queryClient}>
	<Todos />
</QueryClientProvider>
...

앱 전체에서 사용하고자하면 최상위 컴포넌트에 감싸주면 된다. 

 

react - query는 useEffect와 fetch, loading을 이용하는 많은 과정을 생략시켜 준다. 

 

react-query를 이용해서 데이터 패칭을 할 때는 useQuery hooks를 사용하면 된다.

useQuery의 첫번째 인자에는 문자열 혹은 배열값인 queryKey 값을 받게 되어있는데, 해당 queryKey 값으로 데이터를 캐싱하게 된다. 

useQuery(['post',1],...)
useQuery(['pst',2],...)

[예제]

  • isIdle 값은 enabled가 true가 될 때까지, fetch가 시작되기 전까지 true입니다.
  • idle : 쿼리 data가 하나도 없고 비었을 때. {enabled : false} 상태로 쿼리가 호출되었을 때 이 상태로 시작된다.
  • users.ts : axios를 이용해서 users.ts에서는 api를 받아온다. 여기서 data는 User의 배열 타입으로 지정해주고, interface를 통해서 형을 저장해준다.
//users.ts
import axios from 'axios';

const BASE_URL = '<https://jsonplaceholder.typicode.com>';

export interface User{
    id:number;
    name:string;
    username:string;
    email:string;
    address:Address;
    phone:string;
    website:string;
    company:Company;
}
interface Address{
    street:string;
    suite:string;
    city:string;
    zipcode:string;
    geo:Geo;
}
interface Geo{
    lat:string;
    lng:string;
}

interface Company{
    name:string;
    catchPhrase:string;
    bs:string;
}

const axiosUsers = async () =>{
    const response = await axios.get(`${BASE_URL}/users`);
    const data:User[] = response.data;
    return data;
}

export {axiosUsers};
  • Users.tsx : user에 있는 정보를 useQuery 훅을 통해서 받아온다.
// users.tsx
import {useQuery} from 'react-query';
import {axiosUsers} from '../api/users';

const Users = () => {
    const usersQuery = useQuery('users',axiosUsers);

    if(usersQuery.isLoading || usersQuery.isIdle){
        return <div>loading...</div>;
    }

    if(usersQuery.isError){
        return <div>Error...</div>;
    }

    return (
        <div>
            {usersQuery.data.map((user)=>{
                return <div key={user.id}>{user.username}</div>
            })}
        </div>
    )
}

export default Users;
  • App.tsx : React Query를 사용하기 위해서는 QueryClientProvider로 컴포넌트를 둘러싼다.
// App.tsx
import {QueryClient,QueryClientProvider} from 'react-query';
import Users from './components/Users';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Users />
    </QueryClientProvider>
  );
}

export default App;

 

+) 가지고 있는 기능

Devtools : render할 수 있는 component이고 React query에 있는 devtools를 import 해오면 캐시에 있는 query를 볼 수 있다. 

@tanstack/react-query로 바뀌면서 @tanstack/react-query-devtools를 따로 설치해야 한다.

728x90
반응형

+ Recent posts