본문으로 건너뛰기

"web" 태그로 연결된 13개 게시물개의 게시물이 있습니다.

모든 태그 보기

Gateway API 적용 전 – L4/L7, TCP/HTTP and TLS Termination을 이해하기

· 약 9분
Hyunmo Ahn
Front End Engineer @ Line+

동기

이 글은 명확한 주제를 가지고 작성하는 것은 아니다.

Web Server를 운영하는데 K8S(Kubernetes)로, ingress nginx controller를 사용하고 있다. 하지만, 26년 3월까지 maintenance 종료가 예정되어 있어 다음 방법으로 Gateway API를 고민하고 있다.

Gateway API를 고민하며 평소에는 대소롭지 않게 여겼던 네트워크 지식을 몇 개 채우려고 정리한 글이다.

이 글을 보는 여러분들의 지식 욕구를 충족 시켜 줄지는 모르겠지만, 아래 문단의 "질문"이 궁금하다면 글을 계속 읽어보면 좋을지도 모른다. 전문적인 내용은 아니지만, 독자의 흥미를 유발하는 정도의 자극을 주었으면 좋겠다.

질문

Q. LB(Load Balancer)를 사용하는데 L4 LB, L7 LB라는 용어가 자주 보인다. 이 두 가지의 차이는 무엇일까?

Q. Gateway API에서는 HTTP 연결과 TCP 연결을 구분해서 사용한다. 이 차이는 구체적으로 무엇일까?

Q. Gateway API의 TLS 개념을 보게 되면 TLS Mode "Passthrough", "Terminate" 같은 용어가 나온다. 이것들은 어떤 개념일까?

Next.js를 Docker와 Standalone, 그리고 custom server

· 약 13분
Hyunmo Ahn
Front End Engineer @ Line+

Purpose

Next.js 프로젝트를 docker 환경에서 자주 사용한다. 그리고 Next.js의 standalonecustom server를 같이 사용하는데, 각각의 목적과 동작을 이해하지 않으면 dockerizing을 하기 힘들 때가 있으므로 이번에 정리해보려고 한다.

Intro

이번 글에서는 총 4가지 케이스로 dockerizing을 하는 예제를 보여준다. 각 케이스별로 github 예제도 준비해두었으니, 조금 더 스스로 해보고 싶은게 있다면 활용해보길 바란다.

케이스설명StandaloneCustom Server
1기본적인 Next.js 빌드 및 실행
2Standalone 모드로 실행
3Custom Server 사용
4Standalone + Custom Server

왜 React는 Server Component를 만들게 되었을까?

· 약 10분
Hyunmo Ahn
Front End Engineer @ Line+

Purpose

React 18이 2022년에 출시되었고, Nextjs App router가 2023년에 출시되어 이미 케케묵은 조사이지만, nextjs page router와 app router를 쓰며 어떤 점이 달라졌고, 왜 달라졌는지 이유를 찾던 중 app router를 왜 사용하게 되었는지 이유를 찾게 된 글이 있다. 그 글을 읽고 이해한 바를 정리하고자 한다.

Story

글을 읽기 전에 다음과 같은 상황을 가정해보자.

새로운 회사로 이직을 하기 위해 Frontend 개발자 면접자리에서 다음과 같은 질문을 받았다고 하자.
아니면, 새로운 프로젝트 혹은 기존 프로젝트에서 nextjs app router를 사용하자고 팀원들을 설득을 하는 자리이거나,
혼자서 nextjs의 page router와 app router 중 어떤 기술이 적합한지 고민하고 있는 상황이라고 하자.

단순 app router가 새롭게 나온 기술이고, Frontend 개발자로써 app router를 사용해보자는 답안도 충분히 설득력이 있을 수 있다. 하지만, 아래와 같은 질문의 답을 이해하고 있다면, 조금 더 설득력 있는 답변을 할 수 있을 것이다.

Question
  • Q. SSR(Server Side Rendering)과 CSR(Client Side Rendering)의 차이점은 무엇인가?
  • Q. 기존 방식 (nextjs page router)에서 어떤 한계가 nextjs app router 방식을 만들게 되었을까?
  • Q. React에서 Suspense를 사용하는 이유는 무엇일까?

위 질문의 답을 알고 있다면, 본문을 읽지 않아도 무방할 것이고 조금 더 신뢰도가 있는 글을 원한다면 React 18 Architecture 소개 글을 보아도 좋을 것이다. 본문을 읽지 않고 답만 알고 싶다면 Result를 먼저 살펴보자.

다음 본문은 Architecture 글을 읽고, 이해한 바를 필자의 의견을 섞어 정리한 글이다.

Local Storage 용량 초과

· 약 2분
Hyunmo Ahn
Front End Engineer @ Line+

Introduction

프로젝트에서 LocalStorage를 사용했을 때 용량을 고려한 적이 있나요?

localStorage의 용도에 따라 다르겠지만, 대부분의 경우는 용량을 고려하지 않고 사용하게 됩니다. 하지만, 데이터의 성격이 계속해서 쌓이는 구조의 데이터를 저장한다면 데이터는 localStorage의 용량을 초과할 수 있습니다.

초과하면 과연 어떻게 동작할까요?

정답은 Throw 에러 입니다.

Figure 1Local Storage Exceed Error

Solution

위 에러가 발생하면, setStorage 구문은 throw로 인해 종료가 되고 에러 전파를 진행하게 됩니다.

프로젝트에 에러 전파를 잘 처리하고 있다면, 에러 메세지와 함께 에러 로그가 수집 될 것이고 전파를 잘 처리 하지 않았다면 프로젝트가 멈출 수 있습니다.

이러한 상황을 방지하기 위해서는 try-catch 구문을 사용해서 에러를 잡아내고, 에러를 처리하는 로직을 추가해야 합니다.

const setStorage = (key, value) => {
try {
window.localStorage.setItem(key, value);
} catch (e) {
// Stop Throw Error
console.error('Local Storage Exceed Error', e);
}
}

물론 에러가 발생했다는 것은 LocalStorage의 용량을 초과했다는 것이므로 앞으로 사용자는 계속해서 Local Storage Exceed Error가 발생하고, 데이터를 저장하지 못할 것 입니다.

따라서 에러처리 뿐 아니라 데이터를 지워주는 작업까지 추가해주어야합니다.

const setStorage = (key, value) => {
try {
window.localStorage.setItem(key, value);
} catch (e) {
// Stop Throw Error
console.error('Local Storage Exceed Error', e);
window.localStorage.clear();
}
}

React의 이벤트 전파

· 약 6분
Hyunmo Ahn
Front End Engineer @ Line+

Introduction

React 이벤트 핸들러 방식과 vanilla Javascript의 이벤트 핸들러 방식을 혼용해서 사용할 때 이벤트 전파가 의도한대로 동작하지 않을 수 있다. 예를 들면 아래와 같이 button1, button2 구조에서 button2를 클릭했을 때 button1의 이벤트 핸들러도 실행되는 문제가 발생한다.

const buttonEl = document.getElementById('button1');
buttonEl.addEventListener('click', () => {
console.log('button1 clicked');
});

const handleClick = (e) => {
e.stopPropagation();
console.log('button2 clicked');
};

return (
<button id='button1'>
<button id='button2' onClick={handleClick}>Click me</button>
</button>
);
// When button2 is clicked
button2 clicked
button1 clicked

Solution

React에서 이벤트 전파는 vanilla Javascript에서 사용하는 이벤트 전파와 다르다. React에서 이벤트 전파를 delegation 방식으로 처리하기 때문이다. (comment)

React에서는 (React 17 이후로) 이벤트 리스너를 rootDOM에 등록해서 사용한다. 따라서 React끼리의 event listener는 생각한 대로 DOM 구조에 따라 이벤트가 전파가 전달되지만 vanilla Javascript의 이벤트 전파는 생각대로 동작하지 않는다.

가능하다면 두가지 이벤트를 섞어서 쓰지 않는 방향이 좋겠지만, 3rd party library를 사용하거나 제어하지 못하는 부분의 이벤트는 제어하기 힘들기 때문에 동작 방식에 맞춰 이벤트 전파를 막아야한다.

// Unify event listener to vanilla Javascript
const buttonEl = document.getElementById('button1');
buttonEl.addEventListener('click', () => {
console.log('button1 clicked');
});

const button2El = document.getElementById('button2');
button2El.addEventListener('click', (e) => {
e.stopPropagation();
console.log('button2 clicked');
});

return (
<button id='button1'>
<button id='button2'>Click me</button>
</button>
);

// Or
// Unify event listener to React
const handleClick1 = (e) => {
console.log('button1 clicked');
};

const handleClick = (e) => {
e.stopPropagation();
console.log('button2 clicked');
};

return (
<button id='button1' onClick={handleClick1}>
<button id='button2' onClick={handleClick}>Click me</button>
</button>
);

Nextjs에서 server action은 순차적으로 실행된다

· 약 4분
Hyunmo Ahn
Front End Engineer @ Line+

Introduction

nextjs에서 server action은 client 환경에서도 server를 통해 함수를 호출하기 위한 방법으로 사용된다. 이 때, server action은 순차적으로 실행되어 여러 함수를 호출하더라도 하나씩 실행되므로 사용에 유의해야한다.

Behavior

간략하게 요약 된 예시를 보자. 예시는 두가지 함수가 존재한다.

'use server';
import { format } from 'date-fns';

export const callServerFn = async (count)=> {
return new Promise((resolve) => setTimeout(() => {
const time = format(new Date(), 'HH:mm:SS.sss');
console.log(`Server Call #${count}: `, time);
resolve(count)
}, 1000));
}

callServerFn 함수는 응답에 1초가 걸리는 함수이고, 'use server'를 사용하며 server action으로 사용된다. 해당 로직은 next server 에서 이루어진다. 서버 응답 타이밍을 확인하기 위해 log를 찍고있다.

// client.tsx
'use client';

export const CallButton = () => {
const handleClick = async () => {
const result = await Promise.all([
callServer(1),
callServer(2),
callServer(3),
])
}

return (<button onClick={handleClick}>Hello World</button>);
}

CallButton 컴포넌트는 버튼을 클릭하면 callServer 함수를 3번 호출하고, 모든 함수가 완료되면 결과를 반환한다.

Server Call #1:  01:23:22.010
Server Call #2: 01:23:24.011
Server Call #3: 01:23:25.012

버튼을 눌렀을 때 분명 3개의 호출이 동시에 이루어졌지만, log 결과는 하나씩 순차적으로 1초에 한번씩 호출되는 것을 확인 할 수 있다.

CSS로 line-spacing을 구현하는 방법

· 약 2분
Hyunmo Ahn
Front End Engineer @ Line+

Introduction

CSS에서는 줄 간격을 조정하는데 line-height를 사용한다. 하지만 내가 경험했던 프로젝트에서 line-spacing을 지원해야하는 요구사항이 있었다. 이에 대해 CSS로 line-spacing을 구현하는 방법에 대해서 설명하고자 한다.

Line Spacing

Line Gap

Line Gap

Line Gap

Line Spacing (5px)

Line Height

Line Gap

Line Gap

Line Gap

Line Height (fontSize + 5px)

Cheat Sheet

여백이 위, 아래로 나눠서 적용되는 line-height와 달리, line-spacing은 줄 사이에만 여백을 적용한다. 이 때, line-height와의 차이는 줄의 처음과 끝의 여백이 들어가지 않는다는 점이다.

이를 이용해서 margin을 이용하여 line-spacing을 구현할 수 있다.

const lineHeight = 20; // origin line-height
const lineSpacing = 10;
const margin = - (lineSpacing / 2);

const LineSpacingText = () => {
return (
<p style={{
marginTop: `${margin}px`,
marginBottom: `${margin}px`,
lineHeight: `${lineHeight + lineSpacing}px`,
}}>
Hello, World!
</p>
);
};

Playground

Line Spacing 0px

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet.

Line Spacing (0px)

Line Height (0px + fontSize)

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet.

Line Height (0px + fontSize)

nested component를 피해야 하는 이유

· 약 3분
Hyunmo Ahn
Front End Engineer @ Line+

Introduction

프로젝트를 진행하면서 불필요하게 re-render가 발생하는 문제를 만났다.

단순히 render를 하는 것 뿐 아니라 매 render시마다 DOM을 새롭게 다시 그리고 있던 것이 문제였는데, 원인은 nested component로 사용한 코드였다.

따라서 이 글에서는 nested component를 사용하는 것을 피해야 하는 이유를 설명하고자 한다.

Cheat Sheet

// Bad
const List = ({ hasWrapper, borderStyle, children }) => {
const Border = () => {
return <p style={borderStyle}>{children}</p>;
}

if (hasWrapper) {
return (
<Wrapper>
<Border />
</Wrapper>
);
}

return <Border/>;
};
// Good
const List = ({ hasWrapper, borderStyle, children }) => {
if (hasWrapper) {
return (
<Wrapper>
<Border borderStyle={borderStyle}>
{children}
</Border>
</Wrapper>
);
}

return (
<Border borderStyle={borderStyle}>
{children}
</Border>
);
};

const Border = ({ borderStyle, children }) => {
return <p style={borderStyle}>{children}</p>;
}

위와 같이 Border의 코드를 재사용하고, List의 props를 재사용하기 위해 nested component를 사용하게 될 수 있다. 하지만 이런 경우 children은 불필요하게 매 render시마다 새롭게 DOM이 그려지게 된다.

callback function props로 인한 re-render를 피하는 법

· 약 6분
Hyunmo Ahn
Front End Engineer @ Line+

React를 사용하다 보면 function을 component props로 넘길 때 re-render를 피하기 위해 reference 관리에 주의해야 한다. 대부분 useCallback을 사용해서 불필요한 re-render를 피하게 되지만 좋은 대안을 발견하게 되어 이 글을 쓰게 되었다.

영감을 받은 코드는 radix-ui/primitivesuseCallbackRef 이며 이 글에서는 useCallbackRef를 쓰는 케이스와 동작을 이야기 할 예정이다.

아래의 코드는 앞으로 설명 할 useCallbackRef 코드이다. 과연 어떤 상황에서 사용 할 것 같은가?

// useCallbackRef.js
import { useRef, useEffect, useMemo } from 'react';

export function useCallbackRef(callback) {
const callbackRef = useRef(callback);

useEffect(() => {
callbackRef.current = callback;
});

return useMemo(() => ((...args) => callbackRef.current?.(...args)), []);
}

Front-end에서 OAS generator를 어떻게 쓰면 좋을까?

· 약 18분
Hyunmo Ahn
Front End Engineer @ Line+

최근 프로젝트에 OAS-generator를 도입해서 사용하고 있다. 사용하기로 결정할 때 고민이나 확인이 필요한 점이 많았고, 적용 이후 좋은 점도 많은 것 같아서 OAS-Generator 사용 경험에 대해 글을 쓰려한다.

아마 OAS Generator가 무엇인지 궁금한 분들, 알고는 있지만 도입에 고민이 되시는 분들, 이미 사용하고 있지만 잘 사용하고 있는지 망설여지는 분들이 이 글을 읽고 좋은 모티브나 경험을 배워갔으면 좋겠다.

이 글에서 말하고 있는 건 다음과 같다.

  • OAS-generator 가 어떤 것인지.
  • OAS-generator 를 사용했을 때의 장단점.
  • OAS-generator 를 어떻게 사용해야하는지.
    • 설정
    • 커스텀 템플릿
  • 최적화
사전지식
  • Rest API로 Front-end 개발을 진행해 본 경험
  • Mustache 문법을 읽을 수 있는 지식 (Optional)
    • 글을 이해하는데는 필요없지만 실제로 사용한다면 꼭 알아야한다.