기억의 실마리
2025. 8. 9. 18:30

🤔공부하게 된 계기

재직중인 회사의 고객사 요청으로 운영중인 페스티벌 페이지를

새로운 버전으로 만들어 달라는 요청이 있었다.

 

소개페이지에서 동적 animation을 추가해야 하는 과업이 있었는데 이전 버전의

소개페이지에 접속하면 노트북의 펜이 미친 듯이 돌기 시작했고,

내가 직접 개발한 소개페이지는 더 많은 animation이 추가 되었음에도

불구하고 과부화 되어 펜이 돌거나 하진 않았다.

 

이러한 현상의 원인을 [ postioin, margin ] vs [ transform ] 라고 유추했다.

내가 생각한 것은 "Layout 단계에서 지속적으로 재계산 되기 때문?" 이었다.

 

🖼️브라우저 렌더링 순서

  1. Parsing: HTML → DOM Tree, CSS → CSSOM Tree
  2. Style: DOM Tree + CSSOM Tree → Render Tree 생성
  3. Layout(Reflow): Render Tree의 각 노드 위치·크기 계산
  4. Paint: 각 노드를 픽셀 정보로 변환 (Display List 생성)
  5. Composite: 레이어를 GPU로 합성하여 최종 화면 출력

 

🪄transform이 성능이 더 좋았던 이유

margin, padding, width, height, postion 등의 변경으로 animation을 구현하면

[ Parsing ~~ Composite ] 과정을 지속 반복하게 되는데 특히나 Layout단계에서

발생하는 위치의 재계산CPU의 부하를 높이기 때문에 부담스러운 작업이 된다.

 

반면 transform 속성을 활용하여 animation을 구현하게 되면

Composite단계에서 처리하게 되고, 가장 부담스러운 작업인 Layout(Reflow)

단계를 거치지 않기 때문에 압도적인 성능차이가 나게 되는 것이다.

 

결론

animation을 구현한다면 불가피한 상황이 아니라면 transform 속성을 활용하자!

 

✍🏻마치며...

"활용하는 기술에 대한 이해도를 가지자!" 라고 생각하며 개발에 임하고 있지만 막상 시간에 쫓겨 그러지 못하는 경우가 많은 것 같다. 그간 습득했던 기술지식을 기반으로 유추하고 해결하는 방식이 나쁜 것은 아니지만, 알 수 없는 무언가의 영역이 되어 지나치는 것 보다는 학습을 통해 이해도를 높이고 유추가 아닌 확신이 되어가는 것이 베스트인 것 같다.

2025. 8. 3. 17:36

🤔 개발과 함께한지 3년... 벌써?

생각해보면 참 신기하다.

단지 웹사이트를 보고 HTML, CSS만으로 시작한 사이트 클론 퍼블리싱을 시작으로

프론트엔드 개발, 서버 개발, 사이트배포, 개발자로 취업까지 하게 되었다니...!

 

개발을 시작한건 늦다면 늦은 나이 29살 즈음이었지만,

어느 것을 했을 때보다 강한 성취감을 느꼈고, 개발과 함께한 3년이 지난 지금에도

개발에 임할 때의 태도는 변하지 않았으며 매번 나를 성장시켜주고 있다는 생각 또한

변하지 않고 여전히 나와 함께 머물고 있다.

 

개발은 여전히 나에게 가치 있는 삶이 무엇인지 알려준 지표이고

일생을 함께하고 싶은 친구다.

 

그렇기에 앞으로도 개발을 놓치지 않게 최선을 다 하며 살 생각이다.

오래오래 함께 하자!

 

 

🧑🏻‍💻 개발자로서 1년 6개월

지난 게시글 중 SI 회사에 취업에 성공했다는 이야기를한적이 있다.

그렇다... 나는 그 SI회사에서 1년 6개월이라는 시간을 보내고 있다.

 

잠시 개발자 신입( 경력무관 ) 취업 경쟁률을 이야기 해보자면,

그 당시에도 나는 200 : 1 경쟁률로 지금 회사에 입사하였고 얼마전에 회사에서

2명을 채용한다는 채용공고를 올렸는데 350명이 지원을 했다.

이제는 정말 취업이 더욱 쉽지않은 세상이 되어버린 것 같다...

(참고로 우리 회사는 법인 설립 5년이 채 되지 않은 SI 스타트업이다 ...)

 

🖥️Frontend

🌱2024.02.

입사를 하고 당시 회사는 모든 직원이 신입들로만 이루어져있다는 것을 알게 되었다.

아무도 이끌어 줄 사람이 없었고 특히나 팀원 간 커뮤니케이션을 통해 컨벤션을

설립해야 하는데 원할하지 못했던 것 같다.

 

프론트엔드 개발자들과 소통해보니 모두 필요성을 느끼고 있었지만 그 누구도

나서지 않고 있었고 그저 각자 업무가 너무 바쁘다는 이유로 소통비용 조차

쓸 여유가 없다는 것이었다.

 

하지만 나는 전혀 그렇게 생각하지 않는다.

함께 하나의 프로젝트를 완성시켜 나아가는 것이지, 개인 프로젝트가 아니기 때문에

협업은 반드시 커뮤니테이션이 통해 나아가야한다고 생각하기 때문이다.

 

때문에 소통의 중요성을 이야기하며 노력한 끝에 함께 소통하며

컨벤션을 맞추어 개발을 하기 시작했다.

 

이전에 모두 각자 개발하고 제 각각의 네이밍 케이스, 파일 구조를 가지고 있었다면

점진적으로 일관화된 컨벤션이 되어가고, 함께 소통하는 것이 즐겁고 뿌듯했다.

 

🐤 ~ 2025. 08

나는 주로 React의 상태 흐름(state flow), 컴포넌트 트리 구조에 대한 이해를

바탕으로 버그를 fix하는 것과 모듈 의존성 구조에 대한 이해를 바탕으로

참조 순환(circular reference) 문제를 해결하는 등 전체적으로 문제해결에

많은 일을 도맡아 해왔던 것 같다.

 

그리고 UX향상을 위해 많은 노력을 해왔던 것 같다.

컨텐츠가 넘치거나 없을 때의 경우 디자인이 빠져있는 경우가 꽤나 많았고

응답이 지연되는 상황에 대응하는 로딩 스피너, 스켈레톤 대체, 로딩화면 풀커버링 등

누락된 디자인들에 대해 필요성을 이야기하며 추가 디자인을 요구하며 프로젝트를

진행해왔다.

 

개발 부에서는 어색한 CSS의 개선과 tablet사이즈부터 mobile사이즈까지 대응 가능한

폭 넓은 반응형 style을 만들어내고 컴포넌트 Lazy import로 발생하는 Fallback을

커스텀 훅 useSoftNaviate를 구현하여 부드러운 전환을 만들어내는 등

UX향상을 정말 많이 신경쓰며 개발해왔다.

 

디자이너님과 소수 개발자분들은 UX향상 해준 것이 체감되고 중요하다고 말하지만

생각보다 사내 대다수 개발자들은 잘 모르겠다, 개발시간이 더 중요하기 때문에

그럴 시간이 없다 등... 오히려 좋지 않은 시선을 받는 경우도 많았다.

 

UX향상을 위해 보통 10분~ 30분을 더 투자하기도 하고 난해한 경우에는 그냥

야근을 해서라도 UX가 좋은 화면을 구현하고 싶은 것이 이기적인 욕심인가?

라는 생각도 들었다.

( 그리고 기한을 넘겨서 고객사에 피해를 끼친적도 없다 ㅠㅠ )

 

SI기업의 특성상 일정이 빠듯해서 동료들의 입장도 이해가 되긴한다.

 

그리고 지금의 회사에서 모두가 그렇다고 해도 스스로가 만족하지 못한다면 그저

혼자 더 작업을 하면 될 뿐이고, 나는 이러한 생각을 바꾸고싶지 않다 ㅎㅎ

 

💾Backend

뭐야 너 프론트엔드 개발자잖아.

아니다. 이제는 그냥 잡부라고 보면 될 것 같다.

 

기존에 Nest.js로 Node기반 서버를 만들어 직접 메모서비스를 배포해보기도 하였고,

Next.js와 Prisma를 활용한 Serverless Architecture로 개발하여 현재 실제 운영중인

경제학습 사이트도 개발한 경험도 있다.

 

JAVA Spring에서는 아주 단순한 버그나 확장성에 저해되는 구조를 리팩토링해본

경험도 있고 가장 크게 백엔드를 경험한 것은 Python서버를 개발한 경험이다.

 

개발한 Python서버는 사내 프로젝트중에 사업계획서를 분석해서 보완하면

좋을 부분이나 추가하면 좋을 내용 등을 LLM( GPT )을 프롬프트를 활용해서 내용을

반환해주는 일명 " 사업계획서 피드백 챗봇서버 "를 만들었다.

 

사실 이 부분은 대표님께서 신입 직원으로 Python을 다뤄본 컴퓨터공학과 개발자를

채용하였고 그 신입직원분이 초기 개발을 진행했다.

 

그런데 갑자기 그 분이 떠나게 되었다.

급하게 Python 개발자 파견 인력이 필요했고 때문에 신입 직원분은 사무실을 떠나

고객사에 배치되어 파견업무를 시작하게 된 것...

 

아무래도 서버 개발이니 내가 할 이유도 없었고 해도 서버개발자가 할 것 같았다.

하지만 사무실 배치 인력들은 Python을 본적도 없고 할 줄 모른다는 이유로

서버개발자들도 못한다고 단정지으며 안된다고 했다.

 

나는 Nest.js로 서버를 구현한 적이 있고 JAVA를 공부할 때도 사실 큰 흐름에 대한 것은

다르지 않다 라는걸 알고 있기 때문에 하면 할 수 있다 라고 생각했다.

 

결국 모두가 할 수 없다는 결론이 나왔고, 제가 해보겠다고 자진해서 Python서버를

이어서 개발하기로 했다.

 

🥧Python...

나는 프론트엔드 개발자이지만 한 눈에 알아 볼 수 있었다.

큰일났다. 아무리 그래도 그렇지 app.py에 모든 코드를 넣어버릴 줄은......

가장 우선적으로 프로세스, 로직의 플로우를 이해하기가 매우 어려운 상황이었다.

 

그래서 절차적으로 공부 계획을 짰다...

1. 파이썬 언어가 어떻게 작동하는지, 스레드는 어떤 방식으로 활용하는지

2. 파이썬 문법은 어떻게 활용하는지, 지금 코드에서는 어떻게 활용하고 있는 것인지

3. 사용되고 있는 핵심 라이브러리

 

이렇게 절차적으로 이해도를 가진 상태에서는 디자인패턴을 반드시 추가해야겠다는

생각이 들었다.

 

기본적으로 가장 친숙하게 활용해보았던 Nest.js와 유사한 패턴( MVC와 유사 )으로

폴더를 분기하여 유지보수에 더 유리하도록 리팩토링했다.

 

처음 파이썬을 프로젝트를 열어봤을 때 package.json 같은 패키지 관리파일이 없었고

공부하며 알아보니 requirements.txt 라는 폴더로 package list를 export하고 해당

txt파일을 기준으로 install할 수 있는 기능이 있다는 것을 알게 되었다.

 

그리고 전역에 패키지를 인스톨하는 기존 방식에서 venv를 활용하면 프로젝트 단위로

가상 환경에 패키지를 설치할 수 있다는 사실도 알게 되었다...

 

requirements.txt를 활용한 패키지 리스트관리, venv를 활용한 프로젝트 단위의

가상환경 패키지 관리를 설정하였고 얼마나 날것의 코드인지 알게되는 순간이었다...

 

하지만 챗봇 서버 특성상 중간에 요청을 취소할 수 있어야 했는데

기존에 작업했던 모든 라이브러리가 비동기적으로 활용 불가능한 라이브러리들 뿐이었다...

 

기본적으로 동기적으로만 작동한다면 응답 취소가 불가능하다.

또한 블로킹 형태가 되어 동시에 여러명이 요청을 하게 되면 응답시간이 지연되는

문제도 있었다.

 

때문에 http통신, dbconnection 라이브러리를 교체하여 논블로킹 방식으로 리팩토링했다.

pymysql => aiosql

requests => aiohttp

 

그리고 안전하고 정확하게 요청을 취소하기 위해서 직접 worker-pool을 만들어

요청할 때 받은 id를 pool에 가지고 있다가 취소한 경우 정확하게 해당 id요청을

취소할 수 있도록 구현하였다.

 

db커넥션 속도를 최적화 시키기 위해 db pool 또한 구현하여 처리속도를

약 2배 더 향상시켰고 결과적으로 처리속도는 약 4배 빨라졌고 취소 요청도 모두

성공적으로 작동하는 챗봇서버로 재탄생하였다.

 

그리고 토큰검증을 편리하게 활용하기 위해 기존 java spring서버로 구성되어있는

토큰검증만을 실행하는 api를 작업자에게 작업요청하고 이를 활용하기로 했다.

 

해당 api를 데코레이터로 구성하여 동기/비동기요청에 따라 분기처리하고

토큰을 검증하는 AuthGuard데코레이터로 만들어 간단하게 controller 매서드 위에

선언하여 활용하기도 했다.

 

🏠DevOps

⚙️API 자동생성

기본적으로 포지션이 프론트엔드이기 때문에 서버 개발자들과 요청 시 데이터 형과

필요한 데이터들에 대해 소통할 일이 많고 swagger-ui로 요청 데이터의 key, value를 확인하며

fetch api를 제작하게 된다.

 

나는 개발자를 준비하며 Swagger에 작성된 내용을 자동생성하는 OpenApi - generator를

구현한 경험이 있어 이를 사내 프로젝트에서도 기여하고자 했고, mustache파일을 직접 추출해서

경로를 지정하고 mustache파일을 직접 커스텀하여 swagger 기반으로  API를 자동생성할 수 있도록

package.json - scripts에 명령어를 추가하여 소통비용을 줄이기도 했다.

 

화면을 구현하는 것 역시 재미있지만, 개발 환경을 자동화하는 것도 정말 재밌는 것 같다.

 

🤵🏻Jenkins pipeline & 무중단 배포

기존에 구성되어있던 pipline은 대부분 sh파일을 통해서 직접 pid를 관리하며 down-time이

존재하는 레거시 방식으로 구현되어있었다.

 

프론트엔드 빌드파일은 직접 pm2를 활용하여 cluster mode로 무중단배포화 하기 위해

static html파일을 띄우는 것이 아닌, fastify로 구성한 server.js를 구현하고 node환경에서

무중단배포가 가능하도록 구현했고, 서버작업자에게는 Blue/Green 배포 방식으로 변경하는

것을 권장드렸다.

 

근데 1년전에 말한 것 같은데 아직 바뀌지 않은 것 같다...

조만간 내가 eginx에 추가 코드를 작성해서 무중단배포가 가능하도록 구성해야겠다.

 

pipline은 stage를 필요 구성으로 수정하고 각 처리마다 echo 메시지를 넣어 디버깅에 더욱

유리하게 구성하였고 execCommand를 좀 더 상세히 작성해서 서버가 띄워져 있다면 reload,

서버가 없다면 start 등으로 완전 자동화 방식으로 작업환경을 개선했다.

 

✍️마치며...

이렇게 돌아보며 1년 정도 회고를 해보니 정말 많은 경험을 했고 많은 일이 있었던 것 같다... 매번 생각하지만 스스로 개발자를 자처한다면 아무리 자기 포지션이 아니더라도 해낼 수 있다는 자신감을 가지고 뛰어들어보는 것도 정말 좋은 것 같다. 나는 그렇게 성장해왔고 덕분에 매번 개발이 즐겁거운 것 같다.

2025. 8. 2. 17:46

🤔공부하게 된 계기

나는 Window OS를 사용하고 있다.

회사에서 커밋을 할 때  MacOS를 쓰는 분의 코드를 수정하고

커밋하면 파일의 모든 코드가 변경된 파일로 인식이 되어 네이밍만

변경해도 파일 전체가 다시 커밋되어 문제가 있어 알아보게 되었다.

 

📌OS별 개행 방식의 차이?

기본적으로 Window OS에서는 CRLF방식을 활용하여 개행한다.

그리고 Unix, Linux, MacOS는 LF방식을 활용하여 개행하게 된다.

 

각 개행 방식은 아래와 같다.

  • LF = \n
  • CR = \r
  • CRLF:  \r\n

사실상 CR방식은 MacOS 9버전 이하에서 사용했었기 때문에

이제는 거의 취급하지 않는다.

 

🐈‍⬛개행 방식 자동변환 설정

" git config core.autocrlf " 명령어를 통해서 깃에서 커밋할 때

개행 방식 자동 변환에 대한 설정을 할 수 있다.

 

core.autocrlf = input

branch에 checkout( git pull )할 때

개행방식을 변환하지 않고 기존 코드에 녹아든 방식을 유지하고commit할 때 LF 개행방식으로 변환해서 저장소에 올린다.

 

core.autocrlf = false

자동변환 하지 않는다.

 

core.autocrlf = true

Window에서는 checkout했을 때 LF로 받은 경우 CRLF로 변환하고

commit할 때 LF변환하고 커밋하며

MacOS와 Linux에서는 양방향으로 LF로 자동변환 처리한다.

 

  • 명령어
// 마지막 명령어를 true, false, input 등으로 변경하면 적용된다

git config --global core.autocrlf true
git config --local core.autocrlf true

 

 

✅그렇다면 어떻게?

결론적으로 쉽게 설정하기 위해서 MacOS와 Window OS 모두

input으로 설정해주면 된다.

git config --global core.autocrlf input
git config --local core.autocrlf input

 

그럼에도 혹시나 작업환경이 바뀌었을 때 사람인 이상 깜빡할

가능성은 분명 존재한다.

 

🪄프로젝트 자체에서 공통 설정하기

사실 MacOS나 Window, Linux 등의 개행방식도 중요하지만

Git에서 기대하는 개행 값이 LF 개행방식이기 때문에

공동작업자와의 OS 차이까지 생각해보면 LF 개행방식으로

commit을 하는 것이 바람직하다고 볼 수 있다.

 

이를 해결하기 위해 프로젝트 root 경로에 .gitattributes라는

파일을 생성해준다. 그리고 아래 코드블럭처럼 넣어주면

commit할 때 최우선 순위로 autocrlf 설정이 적용되어 강제로

LF로 commit하게 된다.

 

  • .gitattributes
# 모든 텍스트 파일은 자동 감지되고 LF로 저장
* text=auto eol=lf

# 명시적으로 LF 강제할 확장자들
*.js   text eol=lf
*.jsx  text eol=lf
*.ts   text eol=lf
*.tsx  text eol=lf
*.css  text eol=lf
*.scss text eol=lf
*.html text eol=lf
*.json text eol=lf
*.md   text eol=lf
*.yml  text eol=lf
*.yaml text eol=lf
*.sh   text eol=lf
*.env  text eol=lf

# 바이너리 파일은 줄바꿈 처리 제외
*.png  binary
*.jpg  binary
*.jpeg binary
*.gif  binary
*.ico  binary
*.pdf  binary
*.woff binary
*.woff2 binary
*.ttf  binary
*.eot  binary
*.mp4  binary

 

 

✍🏻마치며...

프론트엔드 개발을 지속하며 React의 state flow나 기본적인 script, memory 프로세스들에 대한 이해도는 높아졌지만 막상 다른 부분들에서 취약점이 드러나는 것 같다. 이제 좀 더 확장하여 딥다이브하는 시간을 많이 가져봐야겠다.

 

2025. 5. 4. 18:35

🤔Cross Platform?

크로스플랫폼이란, 서로 다른 디바이스 또는 전혀 다른 OS에도

대응되어 어색함 없이 동일하게 작동될 수 있도록 프로그램( 어플리케이션 )을

개발하는 것을 의미한다.

 

앱개발로 유명한 두가지 프레임워크를 뽑자면 React Native와 Flutter를 꼽을 수 있다.

 

React-Native 일명 RN은 JS로 이뤄져있는 코드를 큰 번들러로 묶어

액션이 일어나면 브릿지로 JS코드를 전달하고 브릿지는 이를 해석하여

OS가 인식할 수 있는 코드로 재번역하여 전달하고 작동(렌더링)하게 된다.

 

즉, RN은 브릿지가 JS로 이루어진 코드를 Native하게 작동할 수 있도록 하는

아주 중요한 역할을 하는 셈이다.

 

그렇다면 Flutter는 어떻게 크로스플랫폼을 지원하고 있을까?

 

🛠️Flutter

플러터는 Google에서 개발된 Dart 언어 기반의 프레임워크이다.

Dart라는 새로운 언어를 배워야 활용이 가능한 부분에서 러닝커브가 있는 편이지만

React, Java, Python 등을 경험해본 입장에서는 크게 어렵지 않았다.

객체지향 방식의 패턴이 오히려 접근성이 좋아 러닝커브가 높다는 생각은 들지 않았던 것 같다.

 

플러터는 RN과 같이 직접적으로 OS와 소통 후 작동(렌더링)하는 방식이 아닌,

별도의 C++로 구성된 엔진위에서 어플리케이션을 그려낸 후 독립적인 형태로

동작하기 때문에 OS간 불일치율이 없다시피 개발이 가능한 것이 강점이다.

 

 

Create Project

flutter create your_project

 

 

Example Code

import 'package:flutter/material.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.black,
        body: Column(
          children: [
            // 박스를 추가하여 간격을 만듦
            const SizedBox(
              height: 80,
            ),

            // 헤더 Row
            Row(
              // Row의 Align을 end로 설정 ( justify-content: end 와 유사 )
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                Column(
                  // 해당 컬럼 item 내부 정렬을 end로 설정 ( justify-content: end 와 유사 )
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: [
                    const Text(
                      "안녕하세요, 홍길동님",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 38,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                    Text(
                      "복귀를 환영합니다.",
                      style: TextStyle(
                          color: Colors.white.withOpacity(0.8), fontSize: 22),
                    ),
                  ],
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}

* 위 예제코드는 " /your_project/lib " 경로에 있는 main.dart 파일에서 활용된 소스코드입니다. 

 

* 예제코드로 나온 결과화면

 

✍️마치며...

새로운 언어, 새로운 프레임워크를 공부하고 개발할 때마다 역시 기술을 공부하며 개발하는 것은 개발자의 평생 숙제라는 것을 다시 한번 깨닫게 되는 계기였다. 여러 언어, 프레임워크를 경험하며 느껴지는 것들은 한가지만 고수하는 것도 분명 역량을 끌어올리기 좋을 수 있지만 다방면의 경험을 쌓는 것 또한 프로그래밍, 개발에 있어 전체적인 흐름을 이해하고 더 넓은 시야로 볼 수 있는 역량이 좋아지고 있는 것 같다. 다시금 느껴지는 것은 개발자라는 직업을 갖게되어 정말 다행이고 행복하다. 앞으로도 이 마음가짐을 잃어버리지 않도록 더욱이 노력해야겠다 ㅎㅎ

'Cross Platform' 카테고리의 다른 글

[ Dart ] 구글에서 만든 객체지향 언어 Dart  (6) 2024.10.06
2025. 1. 1. 19:18

Flutter를 공부하며 프로젝트를 만드려고

cmd에서 create 명령어를 실행해보았다.

 

 

flutter create flutter-intro !!!!

 

 

 

 

...

 

 

 

 

결과는 성공적이지 못했다...

 

 

 

cmd: Error: Unable to find git in your PATH. ㅋㅋ

 

해당 에러는 Git 보안 설정과 연관이 있다고 한다.

때문에 별도로 예외처리를  적용시켜주어야 하는데

특정 경로만을 예외처리 하지 않고 모든 경로에서

가능하도록 명령어를 실행해주었다.

 

git config --global --add safe.directory '*'

 

이제 모든 경로가 안전하다고 명시되었고, 어떠한 경로에서도 create가 가능해졌다.

 

다시한번...

 

flutter create flutter-intro !!!!

 

cmd: ... Try "flutter_intro" instead. ㅋㅋ

 

 

??

 

 

평소 create-react-app, create-next-app에 절여진 나는...

케밥케이스로 네이밍해왔지만 Dart에선 이를 지양하는 것 같았다...

 

 

flutter create를 사용할 땐 스네이크 케이스를 쓰자 !

 

 

사실 보안상 직접 진행하는 프로젝트만 모아둔 경로만 safe한 것이 좋다.

해커가 심어둔 쿠키가 execCommand로 악성 프로그램을 pull 하게 만들든 후 run 시킨다면...?그때부터는 내 컴퓨터가 아니게 될 수도...

2024. 10. 20. 18:46

🤔NextJS 쿠키?

브라우저에 저장되는 일반적인 쿠키와 다를게 없지만

NextJS는 SSR을 지원한다는 점에서

cookie를 서버에서 확인할 수 있는 방법이

제한적이기 때문에 CSR(React 등)만 활용하여

개발하던 개발자들은 생각보다 더 난해한 문제를

겪게 될 수 있다.

 

그런 개발자 중 한명이 필자였고 특히

쿠키를 활용한 JWT 로그인 유지를 구현할 때

interceptor를 통해 매번 토큰을 header에 넣어 요청하고

accessToken이 만료된 경우에는 refreshToken을

서버에 재발급요청하여 재생성된 토큰들을 쿠키에 저장하고

저장된 쿠키를 통해서 이전 요청을 재요청하는 프로세스에서

accessToken 쿠키가 undefined인 상태로 요청이 되는 이슈

꽤나 험난한 삽질과 많은 시간을 고민하게 되었다.

 

결과적으로 NextJS를 제대로 다루기 위해서는

hydration,

middleware,

server - component,

server - actions / mutations,

위 4가지의 작동방식은 필수적으로 학습을 통해

Next가 작동되는 방식에 대해 이해하고 있어야한다는

생각이 들었다.

 

🫗hydration

(hydration에 대한 글은 개인적 주관이 강한 글이기 때문에 넘어가셔도 좋습니다😊)

필자는 단순히 하이드레이션을 주입식 교육처럼

하이드레이션 = 서버의 결과를 클라이언트로 합치는 것

이라고만 생각했지만 결과적으로 이해하게 된 계기는

여러 이슈를 겪으며 어원에 대해 다시 생각하는 것으로

이해도를 갖추게 된 것 같다.

 

hydration이란 수분공급 이라는 어원을 가지고 있다.

그렇다면 어째서 수분공급이지? 라는 의구심을 가지고

좀 더 단순하게 어원에 대해 생각했을 때 식물이 뿌리에서

흡수한 물을 줄기를 통해서 수분공급을 하는 방식을 떠올려 봤다.

 

뿌리 = Server

(땅속에 있어 우리가 표면적으론 볼 수 없지만 핵심이 되는 뿌리)

 

줄기, 잎, 꽃 등 = Client

(땅을 파지 않아도 겉으로 볼 수 있는 식물의 외형)

 

식물은 수분공급이 제대로 이루어지지 않는다면

그 식물은 시들어버리거나 제대로 성장하지 못한다.

 

그렇기 때문에 Server에서 보여주고자 하는 것을

Client에 공급해주어 제대로된 결과물을 보여주는 것이

Hydration이구나! 라는 결론을 내리게 되었다.

 

물론 그저 나의 상상력으로 여기까지 온 것일 수도 있다...

하지만 좀 더 어원에 대해 이해하려할 수록

이해도가 높아지는 경우가 생각보다 많았던 것 같다.

 

만약 영어가 아닌 한글이었다면 조금 더 직관적으로

프로세스를 이해했을 수도 있지만 프로그래밍 언어가

기본적으로 영어로 되어있기 때문에 우리가 프로그래밍에

대해서 이해하기 어렵게하는 하나의 벽이 되고있지 않나...

하는 생각이 들곤 한다.

 

🔗middleware

middleware는 server에서 client에 렌더링되기 이전에

거치게 되는 곳으로 서버와 클라이언트의 교집합 지점이라고

볼 수도 있는 영역이다. 때문에 NextJS를 다룬다면 정말 중요한

기능이기 때문에 꼭 알아야된다고 생각하는 것 중 하나다.

 

NextJS를 활용해서 사용자에게 보여주기전에 server에서

내부적으로 redirect하거나 필수적인 쿠키를 미리 설정

가능하고 국제화를 하는 경우에는 페이지에 접속한

국가에 적합한 언어로 구성된 페이지를 제공하기 위해서

활용하기도 한다.

 

middleware 활용처는 너무 많기 때문에 예제코드는

필자의 깃허브 링크로 남겨두겠습니다.

 

* nextjs14 app-router 국제화를 적용한 템플릿을 예제로 넣었습니다.

Github: https://github.com/zeriong/next14-app-router-i18n-template/blob/master/middleware.ts

 

⚙️server-component

NextJS에서 컴포넌트를 구성할 때 최상단에 "use client"를

작성하지 않는다면 기본적으로 server-component로 작성된다.

그리고 "use client"를 작성한다면 react에서 사용되는 CSR관련

Hooks를 활용할 수 있게 된다.

 

단 여기서 중요한 것은 "use client"라고 해서 SEO가 안되는 것은

아니라는 점이다.

 

html 구성은 server에서 렌더링이 되기 때문에 SEO는

영향을 끼치지 않는다. 단지 CSR에서 활용 가능한

React의 Hooks를 활용해서 CSR에서 작동되는 JS를 활용하겠다.

라고 선언하고 활용하는 것에 불과하다.

 

그러니 "use clinet"를 썼다고 해서 SEO가 안될 것이라는

걱정은 하지 않아도 될 것이다.

(useEffect를 활용하여 state 변경후에 생성되는 컴포넌트 제외)

 

 

🪄server - actions / mutations

서버에서 실행되는 비동기함수를 뜻한다.

form에서 활용하는 action이나 기타 fetch를 활용한

api요청 등을 server에서 작동하게 만드는 것이다.

 

활용방식은 아주 간단하게 해당 함수의 환경 최상단에

"use server" 를 선언하게 되면 이는 server에서 작동하는

함수로 정의된다.

 

"use server"를 정의하는 환경은 주로 파일단위나

함수단위로도 활용 가능하다.

 

상황에 따라서 우리는 다양하게 활용하지만 주로

하나의 파일에서 "use server"를 최상단에

정의하여 모듈화하여 활용하는 경우가 보편적이다.

 

예시적으로 actions.ts 파일을 생성한다고

가정했을 때의 예제 코드이다.

 

  • server-actions 예제코드
// actions.ts

"use server";

/**@desc login server-action */
export async function loginAction(prevState: { success?: boolean; msg?: string }, formData: FormData) {
  const userId = String(formData.get("userId")).trim() as string;
  const userPassword = String(formData.get("userPassword")).trim() as string;

  if (!userId.length || !userPassword.length) return { success: false, msg: "아이디 또는 비밀번호를 확인해주세요" };

  const reqBody = { userId, userPassword };

  const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL_AUTH}/auth/login`, {
    method: "POST",
    body: JSON.stringify(reqBody),
  });

  const resBody = await res.json();

  if (resBody.code != 200) return { success: false, msg: resBody?.message };

  return { success: true, msg: "success" };
}

 

위처럼 server-action을 구성하고 해당 server-action을 로그인을 하는 컴포넌트에서 useFormState 훅을 활용해서 state로 response 결과값을 client에서 동적으로 확인할 수 있다.

 

  • server-mutations 예제코드
// mutations.ts

"use server";

import { getUserInfo } from "@/apis/user";

/** get user data */
export async function getUserData() {
  const res = await getUserInfo();
  return res.body.data;
}

 

정말 단순하게 "use server"를 명시해주어 server에서 해당 api로 fetch를 통해 response 값을 받아볼 수 있다.

 

server - actions / mutations 장점

  1. network 요청 수를 줄일 수 있다
  2. server 내부에서 미리 값을 fetch 받아 렌더링하기 때문에 SEO 향상
  3. API route 없이 서버 측에서 직접 DB에 접근가능
  4. 민감한 데이터를 클라이언트 측에 노출시키지 않을 수 있음
  5. client side 번들 사이즈가 줄어 초기 로딩 속도 개선

 

🍪NextJS 쿠키 트러블 슈팅

내가 맞닥뜨린 이슈는 결국 NextJS에서 쿠키에 대한

hydration이 필요한 상황이었고 가장 첫번 째로 고민한 것은

가능한 것에 대한 고찰이었다.

 

서버에서 쿠키에 접근가능한 방식을 생각해봤을 때

next header에 접근할 수 있어야하는 것이

우선 선행이 되어야 했다.

 

그렇다면 어떠한 경우에 server에서 쿠키에 접근할 수 있는가?

내가 떠올릴 수 있는건 두가지였다.

 

1. middleware의 NextRequest를 통한 접근

2. server-component에서 next-header의 cookies()로 접근

 

1번의 경우는 현실성이 부족했고 2번으로 접근하기로 했다.

 

하지만 interceptor를 구성할 때 따로 api를 요청할 때 마다

지속적으로 재사용해야 하기 때문에 파일로 분기하여

구성할 수 밖에 없었다.

 

단순히 fetch에 interceptor를 구성하여 붙이는 코드로 구성되어있어

최상단에 "use server"를 명시하는 것도 말이 안되고,

그렇다고 interceptor를 server-component에서 구성하는 것도

말이 안된다고 생각이 들었다.

 

그때 마침 생각난 것이 바로 server-component가

next-header에 접근할 수 있는 이유에 대해서 생각해봤다.

 

nextjs14에서는 컴포넌트 구성 시 최상단에

"use client"를 명시하지 않으면 서버컴포넌트로 정의된다.

 

이는 즉 이전 버전에서 사용하던 "use server"가 생략됐을 뿐이고

server - actions / mutations에서 최상단에 "use server"를

명시하고 활용하는 것과 동일하다고 생각이 들었다.

 

그렇다면 방법은 하나 뿐이다.

utils에 server 폴더를 만들고 최상단에 "use server"를 명시한

cookies.ts파일을 만들어 내부에 유틸을 구성하는 방법이다.

 

getServerSideCookie,

setServerSideCookie,

removeServerSideCookie,

 

위 세가지 쿠키관련 유틸을 구성하고

next-header에 접근할 수 있는 환경에서

쿠키를 컨트롤할 수 있도록 만들어주었다.

 

결과는 성공적이었고,

모든 server - actions / mutations와

클라이언트에서의 fetch 요청으로도 성공적인

로그인 유지가 가능한 결과를 얻을 수 있었다.

 

✍️마치며...

개발에 대한 경험이 늘어남에 따라 느끼는 것은 개발은 언제나 정답이 정해져있는 것이고 그 정답에 도달하기 위해선 내가 맞닥뜨린 문제에서 성공적인 예시를 통해 하나씩 이어가는 것이 가장 빠르고 올바르게 이해하며 정확하게 문제를 해결할 수 있는 방법인 것 같다.

2024. 10. 13. 17:28

⌨️단축키 리스트

< 코드 관련 단축키 >

Ctrl + W : 텍스트 확장 선택
Ctrl + D : 선택된 블럭을 하단에 복제
Ctrl + Y : 라인 삭제
Ctrl + Shift + Alt + J : 모든 같은단어 선택
Ctrl + Alt + L : 파일 단위 재정렬
Ctrl + Alt + T : 목록중 코드 감싸기
Ctrl + Shift + ] / [ : 가장 가까운 괄호 시작/종료로 이동
Ctrl + Shift + M : 괄호이동

 

Ctrl + R : 파일단위 지정 글자 replace

Ctrl + Shift + R : 프로젝트 단위 지정 글자 replace


F2 : 워닝이동
Alt + J : 같은단어 차례선택
Shift + F6 : 이름 일괄 변경 하기
Ctrl + F6 : 함수 전체 변경 - 모든것
Ctrl + Shift + backspace : 마지막 작성 코드이동
Alt + Shift + 위,아래: 라인 이동
Alt + 위,아래 : 메소드 단위로 커서 위치 이동

F4 : 정의보기
Ctrl + P : 함수호출시 인수 정보 확인
Ctrl + Alt + F7 : 함수호출하는 곳 찾기
Ctrl + F7 파일내 호출찾기
Ctrl + Alt + F7 모든파일검색
Ctrl + G : 라인번호 이동

 

< 네비게이트 관련 단축키 >

Ctrl + Shift + F : 전체 검색
Ctrl + Shift + N : 파일검색
Ctrl + Shift + Alt + N : 메소드 검색
Ctrl + E : 최근 열었던 파일 목록
Ctrl + F12 : 함수검색

Ctrl + Alt + Shift + T 밑의 팝업 Extract Method
Alt + Delete : 안전한 변수명등 삭제
Ctrl + F6: 함수파라미터 안전 추가
Ctrl + Alt + N : 임시변수에 담긴 값을 값자체로 하나로 단축
Ctrl + Alt + V : 변수만들기
Ctrl + Alt + P : 함수내 하드코딩된 값 파라미터 만들기
Ctrl + Alt + M : 블럭지정하여 함수 작성

 

단축키 출처: https://k-developer.gitbook.io/dev/ide/webstorm/undefined-1

 

 

⭐핵심 단축키

물론 필자 기준이긴 하지만... 아마 보편적으로 많이들 사용하는 단축키라고 생각이든다.

(Ctrl + F, Ctrl + C or V 등은 제외...)

 

Ctrl + D : 선택된 블럭을 하단에 복제

=> 비슷한 유형의 코드 하단에 작성 시 활용

 

Ctrl + W : 텍스트 확장 선택

=> 커서가 있는 텍스트를 일괄 선택할 때

 

Ctrl + E : 최근 열었던 파일 목록

=> 파일 텝을 모두 닫더라도 history를 통해 다시 열 수 있어 유용

 

Ctrl + Tab : 최근 열었던 파일 스왑

=> 특정 파일을 참고하며 만들 때 스왑하며 사용

 

F2 : 워닝(에러) 이동

=> 상단부터 차례로 리팩토링할 때

 

Commit Diff 창에서 F4 : 해당 파일로 이동

=> commit하기 전 diff를 체크하다 수정이 필요한 경우 즉시 파일로 이동

 

Shift + Shift : 파일명, 변수, 함수명 등 전체 검색

=> 주로 파일명을 검색하여 파일을 열고 싶을 때 사용

 

Ctrl + R : 파일단위 지정 글자 replace

=> 네이밍 리팩토링 시 일괄적용을 위해 사용

 

Ctrl + Shift + R : 프로젝트 단위 지정 글자 replace

=> 프로젝트 단위로 지정 이름을 변경할 때 사용

 

Shift + F6 : 이름 일괄 변경 하기

=> 파일, 함수, 변수 등 모든 이름 변경할 때

'IDEA' 카테고리의 다른 글

[ Intellj ] 자주쓰이는 자동완성 단축키  (0) 2024.09.15
2024. 10. 13. 16:27

🤔서론과 계기

회사에서 진행하는 프로젝트에서 useHookForm을

활용 가능하도록 input 공용 컴포넌트를 만들다가

tailwindcss를 통해 스타일을 지정하다"focus:", "hover:" 문득

특정 문자열을 반드시 포함하는 타입을 만들면 어떨까?

라는 생각과 함께 고민해보고 만들어보게 되었다.

 

어떻게 만들면 그게 가능할까 라고 고민하는 순간

가장 먼저 템플릿 리터럴이 떠올랐고 단순하게 작성해봤는데

다행이 해매지않고 원하는 결과를 얻었다.

 

typescript로 고생하는 누군가에게 이 포스팅이 도움이 됐으면 좋겠다.

 

  • 예제코드
// interface 예제
interface IHolder {
  inputPlaceholder?: `placeholder-${string}`;
}

// type 예제
type TFocus = `focus:${string}`;

 

✍️마치며...

개발을 할 때 무언가 필요할 때 검색을 하기전 스스로 떠올려 만들어보는 것이 무엇보다 중요한 것 같다. 정보가 넘쳐나는 시대이기에 더욱 스스로 사고하는 기회가 적어지게 되는 것 같고 이는 개발자에게 치명적이지 않을까? 라는 생각을 하게된다... 앞으로도 초심을 잃지않고 사고력을 꾸준히 키워나아가자!