diff --git a/contentlayer.config.ts b/contentlayer.config.ts index 4058088..010689a 100644 --- a/contentlayer.config.ts +++ b/contentlayer.config.ts @@ -19,10 +19,6 @@ export const Post = defineDocumentType(() => ({ type: 'string', required: true, }, - category: { - type: 'string', - required: true, - }, thumbnail: { type: 'string', required: true, @@ -36,6 +32,34 @@ export const Post = defineDocumentType(() => ({ required: false, }, }, + computedFields: { + id: { + type: 'string', + resolve: (post) => { + const [_, id] = post._raw.flattenedPath.split('/'); + + return id || post._raw.flattenedPath; + }, + }, + + url: { + type: 'string', + resolve: (post) => { + const [_, path] = post._raw.flattenedPath.split('/'); + + return `/posts/${path || post._raw.flattenedPath}`; + }, + }, + + category: { + type: 'string', + resolve: (post) => { + const [category, path] = post._raw.flattenedPath.split('/'); + + return path === undefined ? '기타' : category; + }, + }, + }, })); const contentSource = makeSource({ diff --git a/posts/difference-kakao-sdk-and-url.mdx b/posts/Develop/difference-kakao-sdk-and-url.mdx similarity index 85% rename from posts/difference-kakao-sdk-and-url.mdx rename to posts/Develop/difference-kakao-sdk-and-url.mdx index 1cbbefc..316b6fa 100644 --- a/posts/difference-kakao-sdk-and-url.mdx +++ b/posts/Develop/difference-kakao-sdk-and-url.mdx @@ -1,20 +1,22 @@ --- title: 카카오 로그인의 SDK와 Rest API 방식의 차이점 description: 카카오 로그인 문서를 읽으면서 알게된 카카오 SDK와 Rest API 방식의 차이점을 공유합니다. -category: Develop createdAt: 2023-03-06 -thumbnail: https://user-images.githubusercontent.com/50941453/223130721-65425c5e-d6c3-4b2c-bc59-ac17c24172dc.png +thumbnail: /images/posts/develop/difference-kakao-sdk-and-url/thumbnail.png --- 안녕하세요! 오늘은 카카오 로그인 문서를 읽다가 알게된 **카카오 SDK와 Rest API 방식의 차이점**에 대해서 글을 작성해봅니다. 😀 ## 1. 알아보게된 계기 🔍 + 오늘 오후에 친구랑 같이하는 프로젝트에 관해서 **카카오 로그인 설정** 관련 이야기가 톡방에서 나왔었다. 그리고 어쩌다가 나는 카카오 로그인 공식 개발문서를 보게 되었는데, 공식 개발문서를 보는건 되게 오랜만이었다. -![카카오 로그인 JavaScript 구현 방법](https://user-images.githubusercontent.com/50941453/223125803-7e5b986a-40d8-440f-8f61-7dd50499f694.png) +![카카오 로그인 JavaScript 구현 방법](/images/posts/develop/difference-kakao-sdk-and-url/kakao-login-guide-document.png) + 그러다가 JavaScript를 사용한 구현 방법 문서를 보게되었는데, 평소에 회사에서도 그렇고 **REST API** 방식만을 사용한 나는 거의 처음보는 문서였다. 왜냐하면 REST API 방식으로 사용하면서 단 한번도 문제가 없었기 때문이었다. ### 1-1. REST API 방식이란? 📌 + 카카오 로그인의 REST API 방식은 카카오 인가 코드를 받기위해서 **쿼리 파라미터에 필수값들을 넣어서 만들어진 URL에 직접 접속**후, 리다이렉트 된 페이지에서 인가 코드를 얻는 방식이다. 여기서의 핵심은 URL에 직접 접속한다는점이다.
@@ -29,21 +31,26 @@ const KakaoLoginButton = (): JSX.Element => { return ( ); -} +}; export default KakaoLoginButton; ``` -
아래는 요청 주소의 형식이다. + > https://kauth.kakao.com/oauth/authorize?client_id=(클라이언트 ID)&redirect_uri=(리다이렉트 URI)&response_type=code ### 1-2. 카카오 SDK 방식이란? 📌 + 코드상에서 불러온 자바스크립트 SDK의 **전역 카카오 객체를 사용하여 로그인을 진행**하는 방식이다. 이렇게만 보면 어떤뜻인지 잘 감이 안오니 코드를 통해서 봐보자. ```tsx @@ -59,7 +66,7 @@ const KakaoLoginButton = (): JSX.Element => { kakao?.Auth?.authorize({ redirectUri, // 리다이렉트 URI만 넘겨주고, 해당 주소에서 인가 코드를 받아서 처리 }); - } + }; useEffect(() => { const kakao = (window as any)?.Kakao; @@ -70,14 +77,8 @@ const KakaoLoginButton = (): JSX.Element => { } }, []); - return ( - - ); -} + return ; +}; export default KakaoLoginButton; ``` @@ -85,27 +86,33 @@ export default KakaoLoginButton; SDK를 활용한 로그인 방식도, REST API와 똑같이 리다이렉트된 페이지에서 인가 코드를 받아서 처리하는 방식으로 동일하다. ## 2. 그래서 차이점이 뭘까? 🤔 + 참고로 PC에서 각 방법들을 실행할때는 아무런 차이가 없이 동일하게 동작한다. 하지만 모바일에서 실행해보면 어떨까? 먼저, **Rest API** 방식의 로그인 실행 화면이다. -![카카오 로그인 Rest API 실행 화면](https://user-images.githubusercontent.com/50941453/223123510-78886990-bee8-4e4e-8209-73c7dbab238b.png) +![카카오 로그인 Rest API 실행 화면](/images/posts/develop/difference-kakao-sdk-and-url/rest-api-execute-login.png) 그 다음은 **Kakao SDK** 방식의 로그인 실행화면이다. -![카카오 로그인 Kakao SDK 실행 화면](https://user-images.githubusercontent.com/50941453/223123681-fb6a8184-5c94-46af-b9c1-f20ea2316d8b.png) +![카카오 로그인 Kakao SDK 실행 화면](/images/posts/develop/difference-kakao-sdk-and-url/kakao-sdk-execute-login.png) 사진으로 눈치를 챘을수도 있지만, 정리하면 아래와 같다. > Rest API: 카카오 로그인 페이지를 먼저 접속한 후, **카카오톡으로 로그인** 버튼을 통해서 카카오톡 앱에서 로그인을 할 수 있도록 제공한다. -> Kakao SDK: 휴대폰에 카카오톡 앱이 설치되어있다면 바로 **카카오톡 앱을 실행**하여 로그인을 할 수 있도록 제공한다. 만약 카카오톡 앱이 설치가 안되어있다면, Rest API 방식에서 본 로그인 페이지가 나타난다. +> Kakao SDK: 휴대폰에 카카오톡 앱이 설치되어있다면 바로 **카카오톡 앱을 실행**하여 로그인을 할 수 있도록 제공한다. 만약 카카오톡 앱이 설치가 안되어있다면, Rest API 방식에서 본 로그인 페이지가 나타난다. 곧바로 카카오톡 앱을 실행하여 로그인을 진행한다는 점이 사용자의 전환율을 이끌어내는데 더 도움이 될 수 있는 장점이 있다. 그러나 카카오 쿠키가 존재하면 빠르게 로그인 페이지를 넘어가는 Rest API 방식에 비해, Kakao SDK 방식은 쿠키가 존재하더라도 무조건 카카오톡 앱을 실행하여 로그인하므로 약~간의 단점이 될 수도 있다. (카카오톡 잠금화면 사용자의 경우에만 해당, 잠금화면을 설정하지 않은 사용자는 그냥 물흐르듯이 넘어간다.) ## 3. 마치며 ⏰ + 현재 회사 프로젝트의 카카오 로그인 코드는 **Rest API** 방식으로 구현이 되어있는데, 이를 Kakao SDK를 사용하는 방식으로 교체할지 고민해봐야겠다. +
+ 타입스크립트를 사용하는 나에게는 Kakao SDK 방식이 조금 불편하긴 하다만, 모듈화를 잘해놓으면 큰 문제는 없다고 본다. +
+ 혹시나 글에서 잘못된 부분이 있다면, 피드백 주시면 감사드리겠습니다! 😀 이상으로 글을 마치도록 하겠습니다. 감사합니다. diff --git a/posts/forward-proxy-reverse-proxy.mdx b/posts/Develop/forward-proxy-reverse-proxy.mdx similarity index 82% rename from posts/forward-proxy-reverse-proxy.mdx rename to posts/Develop/forward-proxy-reverse-proxy.mdx index 52d6785..833fe33 100644 --- a/posts/forward-proxy-reverse-proxy.mdx +++ b/posts/Develop/forward-proxy-reverse-proxy.mdx @@ -1,83 +1,121 @@ --- title: Forward Proxy와 Reverse Proxy 정리 description: 프록시 서버의 개념과 포워드 프록시, 리버스 프록시에 대해서 정리해봅시다. -category: Develop createdAt: 2023-05-15 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/054a55c1-2080-4067-b4ee-9b9924e78615 +thumbnail: /images/posts/develop/forward-proxy-reverse-proxy/thumbnail.png --- + 안녕하세요! 오늘은 프록시(Proxy) 서버의 개념과 이를 이용한 포워드 프록시(Forward Proxy)리버스 프록시(Reverse Proxy)에 대해서 알아보겠습니다. ## 1. 프록시 서버란? 🤔 -![프록시 서버의 정의](https://github.com/yiyb0603/yiyb-blog/assets/50941453/f9bb82d0-fa86-45d9-b3d5-e9a309f3bcd9) -먼저 프록시 서버에서 프록시(Proxy)란 어떤뜻을 가지고 있을까요? 영어 단어사전에서 정의된 뜻은 ```대리```라고 정의되어 있는데요. +![프록시 서버의 정의](/images/posts/develop/forward-proxy-reverse-proxy/proxy-server.png) + +먼저 프록시 서버에서 프록시(Proxy)란 어떤뜻을 가지고 있을까요? 영어 단어사전에서 정의된 뜻은 `대리`라고 정의되어 있는데요. +
-```대리```라는 말 그대로입니다. 프록시 서버의 역할은 특정 클라이언트가 자신을 통해서 다른 서비스의 서버로 접속할 수 있게끔 도와주는 서버를 뜻합니다. + +`대리`라는 말 그대로입니다. 프록시 서버의 역할은 특정 클라이언트가 자신을 통해서 다른 서비스의 서버로 접속할 수 있게끔 도와주는 서버를 뜻합니다. +
+ 프록시 서버를 사용하면 보안문제 등으로 인해서 접근할 수 없는 서버를 접속할수 있게끔 해주는 등, 우회 접속을 할 수 있습니다. ### 1-1. 프록시와 VPN의 차이점 ℹ️ -저는 프록시 서버의 정의를 보고나서 ```VPN```과 프록시 서버는 어떤 차이가 있을까? 하는 궁금증이 들었고, 가장 대표적인 차이점은 아래와 같았습니다. + +저는 프록시 서버의 정의를 보고나서 `VPN`과 프록시 서버는 어떤 차이가 있을까? 하는 궁금증이 들었고, 가장 대표적인 차이점은 아래와 같았습니다. - VPN은 장치의 모든 수신, 발신 데이터 보호 - VPN은 트래픽을 암호화 합니다. - VPN은 인터넷 업체의 추적과 정부의 감시를 피하고, 해킹 공격을 방지합니다. VPN과 프록시는 **자신의 실제 IP 주소를 숨긴다**는 점에서는 동일합니다. 그러나 좀 더 세부적인 보안측면에서는 VPN이 더 좋네요. 😲 +
+ 프록시 서버는 클라이언트나 서버 둘중 한곳에서만 사용되는것이 아닌, 양쪽에서 모두 사용될 수 있습니다. 이를 각각 **포워드 프록시**, **리버스 프록시**라고 합니다. +
+ 이 두가지 프록시 서버 방식에 대해서 함께 알아보겠습니다! ## 2. 포워드 프록시 (Forward Proxy) -![포워드 프록시의 정의](https://github.com/yiyb0603/yiyb-blog/assets/50941453/5e9d2407-2a4a-4775-bd0d-bf0b5dea3521) + +![포워드 프록시의 정의](/images/posts/develop/forward-proxy-reverse-proxy/forward-proxy.png) 포워드 프록시 서버는 클라이언트 바로 뒤에 위치하였으며, 클라이언트의 요청을 받아서 요청을 보내야하는 외부 서버에 **대신 접속하여 가져온 데이터를 클라이언트에게 응답해주는 서버**입니다. +
+ 포워드 프록시는 가장 일반적으로 널리쓰이는 프록시 서버 방식이며, 그냥 프록시 서버라고 정의가 되어있는 서버는 대부분 `포워드 프록시` 서버 방식을 사용합니다. +
-만약 우리가 github.com 이라는 주소를 요청했을때, 우리앞에 있는 포워드 프록시 서버가 대신 github.com의 데이터를 받아서 우리에게 전달해준다고 생각하시면 됩니다. -그렇다면 이런 포워드 프록시 방식은 왜 사용하는 것일까요? + +만약 우리가 github.com 이라는 주소를 요청했을때, 우리앞에 있는 포워드 프록시 서버가 대신 github.com의 데이터를 받아서 우리에게 전달해준다고 생각하시면 됩니다. 그렇다면 + +이런 포워드 프록시 방식은 왜 사용하는 것일까요? ### 2-1. 요청 캐싱 + 포워드 프록시 서버는 우리가 특정 웹에 접속했을때, 해당 웹 서버의 정보를 캐싱해둡니다. +
-캐싱을 해둔다면 이전과 동일한 요청에 대해서 서버에 요청을 날리지 않고, 이미 가지고 있는 캐싱 데이터로 빠르게 클라이언트에게 응답을 해줄 수 있습니다. 이는 원 서버의 부하를 줄이는데도 도움이 됩니다. + +캐싱을 해둔다면 이전과 동일한 요청에 대해서 서버에 요청을 날리지 않고, 이미 가지고 있는 캐싱 데이터로 빠르게 클라이언트에게 응답을 해줄 수 있습니다. + +이는 원 서버의 부하를 줄이는데도 도움이 됩니다. ### 2-2. 클라이언트 보안 + 일반적인 공공기관, 학교 등의 네트워크를 이용하다보면 특정 웹사이트에 접속하려고 하면 접속이 제한되는 경우가 종종 있습니다. 왜냐하면 포워드 프록시 서버를 두어서 내부망의 컨텐츠 액세스 규칙을 정의해두었기 때문입니다. +
+ 포워드 프록시 서버에 룰을 추가해서 특정 사이트에 접속하는 것을 막을 수 있습니다. ### 2-3. IP주소 암호화 + 포워드 프록시 서버를 두면 모든 외부 웹사이트 요청에 대해서 프록시 서버를 통해 일어나게 되므로, 자신의 IP 주소를 숨길 수 있습니다. + 만약 외부 서버에서 IP 주소를 추적하려고 하면 포워드 프록시 서버의 IP 주소만 알 수 있게되므로 추적하기가 힘들죠. ## 3. 리버스 프록시 (Reverse Proxy) -![리버스 프록시 서버의 정의](https://github.com/yiyb0603/yiyb-blog/assets/50941453/7f68f2e8-09d4-4681-b000-e0c260cafaa3) + +![리버스 프록시 서버의 정의](/images/posts/develop/forward-proxy-reverse-proxy/reverse-proxy.png) 리버스 프록시 서버는 인터넷 바로 뒤(본 서버 앞)에 위치하였으며, 클라이언트 측에서 보낸 요청을 본 서버 대신 먼저받는 서버이고 **해당 리버스 프록시 서버가 본(main) 서버로 요청을 밀어주는 방식**입니다. +
+ 클라이언트측에게 응답 데이터(Response)를 보내주는것이 포워드 프록시라면, 백엔드측에게 요청 데이터(Request)를 보내주는것이 리버스 프록시라고 정의 할 수 있습니다. +
+ 만약 우리가 github.com 이라는 주소를 요청했을때, github.com의 메인 서버로 곧바로 요청이 보내지는것이 아닌, 서버 앞에 위치한 프록시 서버가 이를 받아서 배후(Reverse)에 있는 서버로 요청을 하게 됩니다. +
+ 그럼 이러한 리버스 프록시 방식을 사용하는 이유는 무엇일까요? ### 3-1. 로드 밸런싱 + 만약 서비스를 운영중인 메인 서버가 3개일때, 한 서버에만 트래픽이 몰리면 어떻게될까요? 그 서버는 죽게 될것입니다. 😲 + 그러나 리버스 프록시 서버를 앞에 두게되면, 트래픽이 여유로운 서버로 요청을 하는 등 서버 밸런스를 맞추는 과정이 이루어지는데 이를 **로드 밸런싱** 이라고 합니다. ### 3-2. 서버 보안 + 클라이언트의 모든 요청은 리버스 프록시 서버 주소로 요청이 되기때문에, **본 서버의 주소를 숨길 수 있습니다.** 오직 본 서버에 접근을 할 수 있는 서버는 리버스 프록시 서버이기에 본 서버 주소를 감출 수 있는 장점이 있죠. ### 3-3. 캐싱 + 리버스 프록시 서버는 클라이언트 요청에 대해서 캐싱을 합니다. 캐싱을 해둔다면 이전과 동일한 요청에 대해서 빠르게 통신할 수 있다. (포워드 프록시의 캐싱 특징과 동일) ## 4. 마치며 📌 + 오늘은 포워드 프록시, 리버스 프록시에 대해서 글을 정리해보았는데요. 최근에 nginx를 만지다가 리버스 프록시 개념이 중요하게 사용되는거같아서 알아보게 되었고, 이렇게 정리를 할 수 있었습니다. +
혹시나 글에서 잘못된 부분이 있다면, 피드백 주시면 감사드리겠습니다! 😀 -
-긴 글 읽어주셔서 감사합니다! \ No newline at end of file +
긴 글 읽어주셔서 감사합니다! diff --git a/posts/giscus-comment-usage.mdx b/posts/Develop/giscus-comment-usage.mdx similarity index 91% rename from posts/giscus-comment-usage.mdx rename to posts/Develop/giscus-comment-usage.mdx index 9b7640e..61fbf44 100644 --- a/posts/giscus-comment-usage.mdx +++ b/posts/Develop/giscus-comment-usage.mdx @@ -1,9 +1,8 @@ --- title: Giscus로 블로그 댓글 시스템 설정 및 Utterances 마이그레이션 하기 description: 간편하고 무료로 사용가능한 Giscus로 댓글 시스템을 설정해보도록 하겠습니다. -category: Develop createdAt: 2023-10-16 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/b75b7a16-769e-4e49-ae5b-6ceddb679666 +thumbnail: /images/posts/develop/giscus-comment-usage/thumbnail.png --- 안녕하세요! 오늘은 `Giscus`를 사용하여 블로그 댓글 시스템을 제공하는 방법에 대해서 알아보겠습니다. @@ -48,7 +47,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/b75b7a16-769e-4
-![Discussion 활성화](https://github.com/yiyb0603/yiyb-blog/assets/50941453/4272e555-b00d-45a3-ad61-24eb64a9d565) +![Discussion 활성화](/images/posts/develop/giscus-comment-usage/check-discussions.png) 가장 먼저 Giscus를 등록할 Repository의 `Discussions`를 활성화 해주어야 합니다. 기본적으로 체크가 해제된 상태일텐데요, 이를 체크해주도록 합니다. @@ -56,11 +55,11 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/b75b7a16-769e-4 그리고 새로고침을 하고나면, Repository 탭에 `Discussions` 라는 탭이 새로 생긴걸 보실 수 있습니다. `Discussions` 탭을 누르고, 카테고리 메뉴로 들어갑니다. -![Discussions 카테고리 메뉴](https://github.com/yiyb0603/yiyb-blog/assets/50941453/92a273ce-2c4d-46a3-8d7f-ea33567b8d97) +![Discussions 카테고리 메뉴](/images/posts/develop/giscus-comment-usage/create-new-category.png) `Discussions`의 카테고리 메뉴로 들어오시면 기본적으로 생성된 카테고리들이 쭉 보이는데요, 새로운 카테고리를 생성하기 위해 New Category 버튼을 눌러줍니다. -![Discussions 카테고리 설정](https://github.com/yiyb0603/yiyb-blog/assets/50941453/71096e3d-2cd2-4738-aef6-7f4b07da8888) +![Discussions 카테고리 설정](/images/posts/develop/giscus-comment-usage/discussion-category.png) 위 사진처럼 카테고리를 생성하는 페이지가 나오는데요. 카테고리 이름과 설명은 자유롭게 하되, `Discussion Format`은 **Announcement**로 설정하는것을 추천드립니다. Announcement는 누구나 토론에 대해 댓글을 작성할 수 있지만, 토론을 생성하는것은 오직 `Repository Owner`만 가능한 권한 시스템입니다. @@ -75,11 +74,11 @@ Announcement는 누구나 토론에 대해 댓글을 작성할 수 있지만, 맨 처음에는 소개글과 작동원리 등이 적혀져있는데요, 스크롤을 조금 내리면 `저장소` 설정란이 보입니다. -![Giscus 저장소 설정](https://github.com/yiyb0603/yiyb-blog/assets/50941453/51e0f7fd-113a-4098-a9ea-751a9cfd0f44) +![Giscus 저장소 설정](/images/posts/develop/giscus-comment-usage/giscus-repository.png) 저장소 입력 형식은 `사용자 이름/레포지토리 이름`으로 작성해주시면 됩니다. 저의 경우엔 `yiyb0603/yiyb-blog`로 작성을 해주었습니다. -![Discussions 설정](https://github.com/yiyb0603/yiyb-blog/assets/50941453/ee15b666-57e0-4b2d-90fe-559ea83e9be8) +![Discussions 설정](/images/posts/develop/giscus-comment-usage/discussion-connect.png) 다시 아래로 스크롤을 내리면 `페이지 - Discussions 연결` 설정이 있는데요. 이 설정은 **어떤 값으로 Discussions 토론을 생성할것인지**를 고르는 설정입니다. @@ -90,14 +89,14 @@ Announcement는 누구나 토론에 대해 댓글을 작성할 수 있지만, Discussions 연결 설정을 끝내고나면 `Discussion 카테고리` 선택 박스가 있는데요. 이는 아까 만들었던 카테고리를 선택하시면 됩니다. -![Giscus 기능, 테마 설정](https://github.com/yiyb0603/yiyb-blog/assets/50941453/ea3d5380-96b4-460a-98cd-d9294bc4e9ba) +![Giscus 기능, 테마 설정](/images/posts/develop/giscus-comment-usage/giscus-settings.png) 마지막으로 편의 기능 및 테마를 설정하는란이 나오는데요. 이부분은 취향껏 선택하시면 됩니다. 저는 기본 기능을 사용하였고, 테마는 라이트 모드일때는 `GitHub Light`, 다크모드일때는 `RStudio Cobalt` 테마를 사용했습니다. ### 2-3. Next.js 코드 연동 📜 -![Giscus 설치 스크립트](https://github.com/yiyb0603/yiyb-blog/assets/50941453/d244b7b8-1be7-4594-819b-1413b3d57f31) +![Giscus 설치 스크립트](/images/posts/develop/giscus-comment-usage/giscus-script.png) 모든 과정을 끝내고 밑으로 스크롤 하시면 위처럼 `script` 코드가 자동으로 생성 되었는데요. 이 코드를 웹 페이지에 등록하면 Giscus 설치는 끝이 납니다. 저는 Next.js에서 연동했기 때문에, Next.js 코드를 작성해보겠습니다. (React.js에서도 거의 동일합니다.) @@ -115,7 +114,12 @@ const PostComment = (): JSX.Element => { useGiscus(commentRef); - return
; + return ( +
+ ); }; export default PostComment; @@ -263,7 +267,7 @@ const useGiscus = (giscusRef: RefObject): void => { }, }, }, - 'https://giscus.app' + 'https://giscus.app', ); }, [theme]); }; @@ -275,7 +279,7 @@ export default useGiscus; 이번 섹션은 기존에 Utterances를 사용하다가 Giscus로 마이그레이션 하는분들을 위한 섹션인데요. `Issues`에 등록되어 있는 댓글을 `Discussions`로 옮기는 방법에 대해서 알려드리겠습니다. -![Issues -> Discussions 옮기기](https://github.com/yiyb0603/yiyb-blog/assets/50941453/8d50f33b-cdfb-47c4-8d44-cb4cb91e1f37) +![Issues -> Discussions 옮기기](/images/posts/develop/giscus-comment-usage/issue-to-discussion.png) 먼저 옮기고자 하는 Issue로 들어간다음 오른쪽 하단의 `Convert to discussion`을 누르고, 처음에 생성한 Discussions 카테고리를 선택하면 끝입니다. diff --git a/posts/github-actions-reusable-workflow.mdx b/posts/Develop/github-actions-reusable-workflow.mdx similarity index 95% rename from posts/github-actions-reusable-workflow.mdx rename to posts/Develop/github-actions-reusable-workflow.mdx index 40f920c..f857f3c 100644 --- a/posts/github-actions-reusable-workflow.mdx +++ b/posts/Develop/github-actions-reusable-workflow.mdx @@ -1,9 +1,8 @@ --- title: Reusable Workflow로 GitHub Actions 워크플로우 재사용하기 description: GitHub Actions의 재사용 가능한 워크플로우를 사용하여 로직을 재사용 해보겠습니다. -category: Develop createdAt: 2023-10-19 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/d9dca16f-3950-422f-a050-e9ed65105d17 +thumbnail: /images/posts/develop/github-actions-reusable-workflow/thumbnail.png --- 안녕하세요! 오늘은 GitHub Actions의 `Reusable Workflow`를 사용하여 워크플로우 로직을 재사용하는 방법을 알아보겠습니다. @@ -77,11 +76,11 @@ jobs: 먼저 `Reusable Workflow`를 저장해둘 Repository를 하나 만들어줍시다. 저는 **reusable-workflows**라는 이름으로 만들었어요. -![Repository 생성](https://github.com/yiyb0603/yiyb-blog/assets/50941453/ccb8e492-e4eb-4beb-8634-3dc8792c0fc1) +![Repository 생성](/images/posts/develop/github-actions-reusable-workflow/create-repository.png) 그러고나서 `.github/workflows/reusable-workflow.yaml` 경로에 파일을 만들고, 두번째 섹션에서 제가 작성한 코드를 붙여넣어주세요. -![Workflow 파일 추가](https://github.com/yiyb0603/yiyb-blog/assets/50941453/05c86e63-1b4e-4291-8ad6-99d3c3eb4da8) +![Workflow 파일 추가](/images/posts/develop/github-actions-reusable-workflow/create-workflow-file.png) ### 3-1. 액세스 허용하기 ✅ @@ -89,7 +88,7 @@ jobs: 액세스를 허용하는 방법은 정말 간단합니다. `Repository 설정 -> Actions -> General` 설정 페이지로 이동한다음, 맨 밑으로 스크롤 하시면 아래의 설정란이 있습니다. -![GitHub Actions 액세스 허용](https://github.com/yiyb0603/yiyb-blog/assets/50941453/6d97d4e9-d500-42a8-b696-293838365af3) +![GitHub Actions 액세스 허용](/images/posts/develop/github-actions-reusable-workflow/workflow-access.png) 이 설정란이 무엇이냐면 다른 Repository의 워크플로우에서 해당 Repository의 `Reusable Workflow`에 접근가능 여부를 설정하는데요. 저희의 목적은 재사용이므로 기본적으로 선택된 **Not accessible**을 선택 해제하고, **Accessible from repositories owned by the user**를 선택한다음 `Save 버튼`을 눌러줍니다. @@ -135,13 +134,13 @@ jobs: `with` 필드에는 `Reusable Workflow`에서 정의한 인자를 넘길 수 있습니다. 만약 VSCode를 사용하실때 [GitHub Actions Extensions](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-github-actions)를 설치하시면, `required` 인자에 대해서 오류를 캐치할 수 있으므로 정말 유용합니다. 강력 추천드려요 👍 -![GitHub Actions Extensions](https://github.com/yiyb0603/yiyb-blog/assets/50941453/dba853ca-53c5-4d46-b497-22c7043db42b) +![GitHub Actions Extensions](/images/posts/develop/github-actions-reusable-workflow/vscode-extensions.png)
저는 `main` 브랜치에서 `push` 이벤트가 일어났을때 Actions를 실행하기로 정의해놨습니다. 커밋 후 워크플로우의 결과를 봅시다. -![워크플로우 실행 결과](https://github.com/yiyb0603/yiyb-blog/assets/50941453/5dfdc6a8-c409-4717-b2e0-c35411559e50) +![워크플로우 실행 결과](/images/posts/develop/github-actions-reusable-workflow/workflow-result.png) `with` 필드에 넘겨준 **나는 Actions Project 레포**라는 값이 잘 담겨서 나옵니다. 이런식으로 워크플로우에 인자를 넘겨서 활용할 수 있어요. @@ -193,6 +192,7 @@ jobs:
**두번째**는 시크릿 값을 통째로 넘겨서 `Reusable Workflow`에서 `secrets`에 접근할 때 자신이 호출된 Repository의 시크릿 값을 참조하도록 적용할 수 있습니다. + 제가 생각했을때 이 방법을 쓰는 경우는 `Organization Secrets`를 사용하지 못하는 환경이고, Repository마다 시크릿 값이 차이가 없는 경우에 사용할 수 있을것 같네요. ```yaml diff --git a/posts/msw-usage.mdx b/posts/Develop/msw-usage.mdx similarity index 94% rename from posts/msw-usage.mdx rename to posts/Develop/msw-usage.mdx index 9948864..1fa6edc 100644 --- a/posts/msw-usage.mdx +++ b/posts/Develop/msw-usage.mdx @@ -1,9 +1,8 @@ --- title: MSW를 사용하여 API Mocking 하기 description: 프론트엔드 측에서 백엔드 API를 Mocking하여 더 효율적으로 개발을 해봅시다. -category: Develop createdAt: 2023-12-04 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/d2d7eeae-401c-4219-8fd2-85c5883312d3 +thumbnail: /images/posts/develop/msw-usage/thumbnail.png --- 안녕하세요! 오늘은 백엔드 API를 Mocking 해주는 라이브러리인 `MSW`란 무엇이며, 사용방법에 대해서 알아보도록 하겠습니다. @@ -18,7 +17,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/d2d7eeae-401c-4 하지만 이를 보완하기 위해서 서버의 API가 개발 완료될때까지 클라이언트단에서 **모의로 동작**할 수 있게끔 설정을 해두면 어떨까요? 마치 실제 API와 통신이 이루어지는것처럼 코드를 작성할 수 있고, 추후에는 코드를 최소한으로 건드리며 실제 API로 바꿔칠 수 있다면 프론트엔드 개발자들은 시간을 더 효율적으로 사용할 수 있습니다. -![Mocking Service Worker](https://repository-images.githubusercontent.com/157397583/df5e7b5a-3bf9-4521-8f57-4d8ca4e3a3fe) +![Mocking Service Worker](/images/posts/develop/msw-usage/msw.png) > 공식문서 바로가기: https://mswjs.io @@ -190,11 +189,11 @@ export default App; 과연 API의 응답값이 저희가 임의로 반환한 값으로 응답이 올까요? -![MSW Mocking 실행 결과](https://github.com/yiyb0603/yiyb-blog/assets/50941453/68352bf6-978b-4d91-be14-8fe09e9269b1) +![MSW Mocking 실행 결과](/images/posts/develop/msw-usage/msw-get-response.png) 원래 JSONPlaceholder API를 호출했을때 임의의 100개 데이터를 전달해주는 반면, API Mocking을 설정해두니 저희가 임의로 반환한 데이터가 올바르게 응답되었네요! 동시에 네트워크 탭을 보시면 `200 OK`가 뜨면서 서비스 워커에서 반환되었음을 알 수 있습니다. -![서비스 워커 반환](https://github.com/yiyb0603/yiyb-blog/assets/50941453/3282b193-b381-4b6e-89e6-9cdbe4df7039) +![서비스 워커 반환](/images/posts/develop/msw-usage/from-service-worker.png) ### 2-3. POST Mocking @@ -280,7 +279,7 @@ POST 요청에 대해서도 API Mocking이 성공했을까요? 네! JSONPlaceholder API로 요청하는 데이터를 Mocking 서버에서 가로챈 후, 응답을 성공적으로 받을 수 있었습니다. -![POST API Mocking](https://github.com/yiyb0603/yiyb-blog/assets/50941453/f632d59d-7d0e-4ad3-af50-df4c83542ded) +![POST API Mocking](/images/posts/develop/msw-usage/msw-post-response.png) API Mocking이 적용되는 과정을 아래처럼 요약할 수 있습니다. diff --git a/posts/useful-http-cache.mdx b/posts/Develop/useful-http-cache.mdx similarity index 94% rename from posts/useful-http-cache.mdx rename to posts/Develop/useful-http-cache.mdx index 6b2edee..7cfadbc 100644 --- a/posts/useful-http-cache.mdx +++ b/posts/Develop/useful-http-cache.mdx @@ -1,9 +1,8 @@ --- title: 자주 사용되는 HTTP 캐시 옵션 알아보기 description: 웹 서비스에서 자주 사용되는 HTTP 캐시 옵션에 대해서 알아봅시다. -category: Develop createdAt: 2023-11-02 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/c04ef50d-f878-4a7d-8dfc-a2dccc195929 +thumbnail: /images/posts/develop/useful-http-cache/thumbnail.png --- 안녕하세요! 오늘은 웹 서비스에서 자주 사용되는 `HTTP 캐시` 옵션에 대해서 알아보도록 하겠습니다. @@ -68,7 +67,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/c04ef50d-f878-4 주로 원본 서버(Origin Server)에서 사용됩니다. -![브라우저, 중간서버, 원본서버 관계도](https://github.com/yiyb0603/yiyb-blog/assets/50941453/6bf6f934-8779-4fcf-ad36-4c2e21ca9614) +![브라우저, 중간서버, 원본서버 관계도](/images/posts/develop/useful-http-cache/browser-server-relation.png)
@@ -132,7 +131,7 @@ HTML 파일은 항상 동일한 URL을 가지고 있기 때문에 내용이 변 ### 3-2. CSS, JS 파일 -![항상 고유한 URL을 가진 CSS, JS 파일](https://github.com/yiyb0603/yiyb-blog/assets/50941453/2a1c91d3-825f-4640-9a2b-5237072c40e8) +![항상 고유한 URL을 가진 CSS, JS 파일](/images/posts/develop/useful-http-cache/unique-files.png) Next.js에서 빌드를 하고나면 모든 **CSS, JS 파일들은 고유한 URL**을 가지고 있기 때문에 배포를 통해서 교체되면 URL이 자동으로 변경되어 새로운 캐시가 생성됩니다. CSS와 JS 파일은 절대 변경될 수 없는 캐시라고 생각하고 넉넉한 `max-age (약 1년)`를 지정합니다. @@ -146,7 +145,7 @@ Next.js에서 빌드를 하고나면 모든 **CSS, JS 파일들은 고유한 URL ## 4. CDN Invalidation ♻️ -![CloudFront CDN 무효화](https://github.com/yiyb0603/yiyb-blog/assets/50941453/5cab08f5-149a-446b-9c51-cbb079494078) +![CloudFront CDN 무효화](/images/posts/develop/useful-http-cache/cdn-invalidation.png) 중간 서버의 캐시를 초기화 하기 위해서 대부분의 CDN에서는 `CDN 무효화(Invalidation)`를 제공하고 있습니다. 대표적인 CDN 플랫폼 `CloudFront`에서도 위처럼 무효화를 지원하는데요. 무효화 하려는 리소스의 경로를 **와일드카드 패턴**으로 지정해줄 수 있습니다. diff --git a/posts/utm-parameters.mdx b/posts/Develop/utm-parameters.mdx similarity index 66% rename from posts/utm-parameters.mdx rename to posts/Develop/utm-parameters.mdx index f95c557..9533aef 100644 --- a/posts/utm-parameters.mdx +++ b/posts/Develop/utm-parameters.mdx @@ -1,102 +1,128 @@ --- title: 온라인 마케팅의 핵심, UTM이 무엇일까? description: 온라인 마케팅의 효과를 분석, 추적할 수 있도록 도와주는 UTM 파라미터에 대해서 알아봅시다. -category: Develop createdAt: 2023-06-07 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/a4c3882f-1871-49a1-94a2-7a2d4b19b83b +thumbnail: /images/posts/develop/utm-parameters/thumbnail.png --- 안녕하세요! 오늘은 온라인 마케팅에서 자주 사용되는 **UTM 파라미터**란 무엇이며, 이를 사용한 온라인 마케팅에서의 활용 방법에 대해서 알아보도록 하겠습니다. 🔍 ## 1. UTM 파라미터란? 🤔 + `UTM`은 **Urchin Tracking Module** 이라는 단어를 약자로 줄여서 부르며, 웹사이트 주소에 **쿼리 파라미터 형식**으로 뒤에 붙어서 동작합니다. 이때 붙여진 UTM 파라미터는 온라인 마케팅의 효과를 추적 및 분석 가능하도록 해줍니다. +
+ `UTM` 파라미터는 해당 마케팅 소재를 통해서 `어느 곳에서 (Source)`, `어떻게 (Medium)`, `언제 (Campaign)`, `무엇을 통해서 (Content)`, `무슨 키워드로 (Term)`와 같은 유입정보를 수집하는데에 도움이 됩니다. -
-> **UTM 파라미터를 적용한 웹사이트 URL 예시는 아래와 같이 나타낼 수 있습니다.** -[https://naver.com?utm_source=instagram&utm_medium=story&utm_campaign=2023_06](https://naver.com?utm_source=instagram&utm_medium=story&utm_campaign=2023_06) +> **UTM 파라미터를 적용한 웹사이트 URL 예시는 아래와 같이 나타낼 수 있습니다.** > [https://naver.com?utm_source=instagram&utm_medium=story&utm_campaign=2023_06](https://naver.com?utm_source=instagram&utm_medium=story&utm_campaign=2023_06) UTM 파라미터 명으로 사용할 수 있는 속성은 대표적으로 5개가 있습니다. -|이름|설명|필수 여부| -|---|---|---| -|utm_source|어디서 유입이 되었는가?|O| -|utm_medium|어떻게 유입이 되었는가?|O| -|utm_campaign|언제 유입이 되었는가?|O| -|utm_content|무엇을 통해서?|X| -|utm_term|무슨 키워드로?|X| +| 이름 | 설명 | 필수 여부 | +| ------------ | ----------------------- | --------- | +| utm_source | 어디서 유입이 되었는가? | O | +| utm_medium | 어떻게 유입이 되었는가? | O | +| utm_campaign | 언제 유입이 되었는가? | O | +| utm_content | 무엇을 통해서? | X | +| utm_term | 무슨 키워드로? | X | 이제 표에서 설명드린 5개의 속성에 대해서 알아보겠습니다. ### 1-1. utm_source 🚀 + `utm_source` 속성은 **어디서 유입이 되었는가**를 분석할 수 있는 속성이며, 해당 마케팅 광고의 **최상위 위치**에 해당되는 값이 들어있습니다. +
+ 최상위라는 말은 즉 광고가 노출된 플랫폼 매체를 의미합니다. 대표적으로 `네이버`, `페이스북`, `인스타그램`, `구글` 등이 들어갈 수 있는데요, 아래의 예시를 통해서 `utm_source`의 활용 예제를 살펴보겠습니다. > Q. 네이버 홈페이지에서 광고 배너를 통해 유입시키려고 한다. 이때 utm_source는 무엇인가? -A. **정답: 네이버(naver)**, 광고 배너는 하나의 마케팅 방법에 해당되기 때문에 플랫폼 매체를 의미하는 **네이버**가 해당됩니다. -ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=naver](#) +> A. **정답: 네이버(naver)**, 광고 배너는 하나의 마케팅 방법에 해당되기 때문에 플랫폼 매체를 의미하는 **네이버**가 해당됩니다. +> ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=naver](#) > Q. 페이스북 피드 네이티브 광고를 통해 유입시키려고 한다. 이때 utm_source는 무엇인가? -A. **정답: 페이스북(Facebook)**, 네이티브 광고 또한 하나의 마케팅 방법이므로 플랫폼 매체를 의미하는 **페이스북**이 해당됩니다. -ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=facebook](#) +> A. **정답: 페이스북(Facebook)**, 네이티브 광고 또한 하나의 마케팅 방법이므로 플랫폼 매체를 의미하는 **페이스북**이 해당됩니다. +> ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=facebook](#) ### 1-2. utm_medium 📘 + `utm_medium` 속성은 **어떻게 유입이 되었는가**를 분석할 수 있는 속성이며, 해당 마케팅 광고의 **노출 방식**에 해당되는 값이 들어있습니다. +
+ 노출 방식이란, 해당 마케팅 플랫폼에서 제공하는 방식들 중 어느 노출 방식을 사용하느냐인데요. 네이버의 `배너 광고`, 페이스북과 인스타그램의 `스폰서 광고`, 카카오톡의 `톡 메신저` 등등 다양한 플랫폼에서 제공되는 다양한 광고 방식이 이에 해당됩니다. +
+ 이번에도 아래 예시를 통해서 예제를 알아봅시다. > Q. 네이버 홈페이지의 디스플레이 광고 배너를 통해서 유입시키려고 한다. utm_medium은 무엇인가? -A. **정답: 디스플레이(display)**, 디스플레이는 네이버 광고 방식중 하나이므로 해당된다. -ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=naver&utm_medium=display](#) +> A. **정답: 디스플레이(display)**, 디스플레이는 네이버 광고 방식중 하나이므로 해당된다. +> ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=naver&utm_medium=display](#) > Q. 카카오톡 채널 메세지를 통해서 유입시키려고 한다. utm_medium은 무엇인가? -A. **정답: 메세지(Message) 혹은 채널 메세지(Channel Message)**, 채널 메세지 홍보는 카카오 광고 방식중 하나이므로 해당된다. -ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=kakaotalk&utm_medium=message](#) +> A. **정답: 메세지(Message) 혹은 채널 메세지(Channel Message)**, 채널 메세지 홍보는 카카오 광고 방식중 하나이므로 해당된다. +> ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=kakaotalk&utm_medium=message](#) ### 1-3. utm_campaign 📆 + `utm_campaign` 속성은 **어떤 캠페인을 통해서 유입 되었는가**, 다른말로 **언제 유입 되었는가** 라고도 해석할 수 있습니다. +
+ 마케팅 `캠페인`은 해당 마케팅의 이름이나 제목을 넣거나, 해당 마케팅이 일어난 시점을 적기도 하는등으로도 사용할 수 있습니다. +
+ 이번에도 아래 예시를 통해서 예제를 알아봅시다. > Q. 2023년 11월에 인스타그램 스폰서 광고를 통해서 유입시키려고 한다. utm_campaign은 어떻게 쓸 수 있을까? -A. **유사 정답: 2023_11_campaign**, 2023년 11월경에 일어난 마케팅임을 utm_campaign에 지정할 수 있다. -ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=instagram&utm_medium=sponsor&utm_campaign=2023_11_campaign](#) +> A. **유사 정답: 2023_11_campaign**, 2023년 11월경에 일어난 마케팅임을 utm_campaign에 지정할 수 있다. +> ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=instagram&utm_medium=sponsor&utm_campaign=2023_11_campaign](#) > Q. 2023년 11월에 카카오톡 채널 메세지를 통해서 서비스에 회원가입하지 않은 사용자들을 전환시키는 마케팅을 하려고 한다. utm_campaign은 어떻게 쓸 수 있을까? -A. **유사 정답: 2023_11_none_user_convertion 혹은 시기를 생략 할 수도 있을듯하다.**, 해당 마케팅의 이름을 utm_campaign으로 사용할 수 있다. -ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=kakaotalk&utm_medium=message&utm_campaign=2023_11_none_user_convertion](#) +> A. **유사 정답: 2023_11_none_user_convertion 혹은 시기를 생략 할 수도 있을듯하다.**, 해당 마케팅의 이름을 utm_campaign으로 사용할 수 있다. +> ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=kakaotalk&utm_medium=message&utm_campaign=2023_11_none_user_convertion](#) ### 1-4. utm_content 🖋️ + `utm_content` 속성은 **무슨 소재의 마케팅을 통해서 유입 되었는가**를 분석할 수 있는 속성입니다. +
+ 위에서 알려드린 `utm_campaign` 속성과 연관되어있는 속성이라고 저는 생각되는데요, 만약 동일한 캠페인명으로 여러개의 광고가 집행된다면 각 광고별 성과를 측정하기위한 도구가 바로 `utm_content` 속성입니다. +
+ 이번에도 아래 예시를 통해서 예제를 알아봅시다. > Q. 2023년 11월에 인스타그램 스폰서 광고를 통해서 "할인 이벤트"에 관한 광고를 집행하려한다. utm_content는 어떻게 쓸 수 있을까? -A. **정답: sale_event**, 할인 이벤트라는 주제를 나타내는 마케팅이므로 해당된다. -ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=instagram&utm_medium=sponsor&utm_campaign=2023_11_campaign&utm_content=sale_event](#) +> A. **정답: sale_event**, 할인 이벤트라는 주제를 나타내는 마케팅이므로 해당된다. +> ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=instagram&utm_medium=sponsor&utm_campaign=2023_11_campaign&utm_content=sale_event](#) > Q. 2023년 11월에 네이버 뉴스 페이지 광고에 "오픈 이벤트"에 관한 광고를 집행하려한다. utm_content는 어떻게 쓸 수 있을까? -A. **정답: open_event**, 오픈 이벤트라는 주제를 나타내는 마케팅이므로 해당된다. -ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=instagram&utm_medium=sponsor&utm_campaign=2023_11_campaign&utm_content=open_event](#) +> A. **정답: open_event**, 오픈 이벤트라는 주제를 나타내는 마케팅이므로 해당된다. +> ℹ️ 전체 URL: [https://yiyb-blog.vercel.app?utm_source=instagram&utm_medium=sponsor&utm_campaign=2023_11_campaign&utm_content=open_event](#) ### 1-5. utm_term 🔍 + `utm_term` 키워드는 **어떤 키워드를 통해서 유입 되었는가**를 분석할 수 있습니다. `utm_term`은 주로 검색광고에서 활용되며, 각종 마케팅 플랫폼으로 검색광고를 등록하려고 할때는 거의 필수적으로 넣어주어야 합니다. (구글, 네이버 등등) +
+ 일반적인 SNS 홍보에서는 잘 사용되지 않는것 같네요. > 예시 URL: [https://yiyb-blog.vercel.app?utm_source=google&utm_term=타입스크립트](#) ## 2. 마치며 📌 + 오늘은 온라인 마케팅에서 핵심적인 개념으로 여겨지는 **UTM 파라미터**에 대해서 알아보았습니다. 저의 경우에는, 위에서부터 소개해드린 순서대로 **밑에 소개된 속성이 위에 소개된 속성의 하위 개념이다.** 라고 생각해보니까 이해가 잘 되는거 같더라고요. 👍 +
+ 이번 글을 읽으시고 나서 이해가 잘 되었다면 좋겠습니다! ㅎㅎ 😃 앞으로 종종 마케팅 지식과 관련한 글도 써나가면 좋을듯 하네요. +
-긴 글 읽어주셔서 감사합니다! 잘못된 부분에 대해서 피드백은 언제나 환영입니다 🤓 \ No newline at end of file + +긴 글 읽어주셔서 감사합니다! 잘못된 부분에 대해서 피드백은 언제나 환영입니다 🤓 diff --git a/posts/noopener-and-noreferrer.mdx b/posts/HTML/noopener-and-noreferrer.mdx similarity index 79% rename from posts/noopener-and-noreferrer.mdx rename to posts/HTML/noopener-and-noreferrer.mdx index 093fcc4..ad0718d 100644 --- a/posts/noopener-and-noreferrer.mdx +++ b/posts/HTML/noopener-and-noreferrer.mdx @@ -1,30 +1,38 @@ --- title: HTML 하이퍼링크의 noopener와 noreferrer이란? description: 하이퍼링크 태그의 rel 속성에 사용할 수 있는 noopener와 noreferrer에 대해서 알아봅시다. -category: HTML createdAt: 2023-08-29 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/8b097645-93ca-4338-9c28-7502076b03ac +thumbnail: /images/posts/html/noopener-and-noreferrer/thumbnail.png --- 안녕하세요! 오늘은 HTML 하이퍼링크 태그에서 사용되는 `noopener`와 `noreferrer` 속성에 대해서 자세하게 알아보도록 하겠습니다. ## 1. 하이퍼링크 태그 🪢 + HTML 문서에서 하이퍼링크를 연결하기 위해서는 `a` 태그를 사용하여 하이퍼링크를 연결할 수 있습니다. 아래처럼 말이죠. +
+ `href` 속성으로 이동할 링크를 지정하고, `target` 속성으로 새창여부를 제어하는 코드는 다들 작성해보셨을겁니다. ```html - + - - + + Document 네이버로 이동하는 링크 - + 구글로 이동하는 링크 (새창으로 열림) @@ -43,24 +51,30 @@ const MyComponent = () => { 새창과 네이버로 연결하려고 합니다. ); -} +}; export default MyComponent; ``` 위 컴포넌트는 겉보기에는 아무 문제가 없습니다. 실제로 컴파일 오류가 나는것도 아니고요. 그러나 위처럼 작성했을때, 몇년전까지는 리액트 린트에서 노란줄을 표시하면서 경고를 띄웠습니다. (지금은 경고를 띄우지 않네요.) +
-이유는, **target=_blank**가 지정된 a 태그의 `rel` 속성에 `noopener`와 `noreferrer`가 지정되지 않아서 리액트 린트에서 경고를 띄웠었는데요. 그렇다면 이 `noopener`와 `noreferrer` 속성이 무엇일까요? + +이유는, **target=\_blank**가 지정된 a 태그의 `rel` 속성에 `noopener`와 `noreferrer`가 지정되지 않아서 리액트 린트에서 경고를 띄웠었는데요. 그렇다면 이 `noopener`와 `noreferrer` 속성이 무엇일까요? ## 2. noopener 📵 -> **target=_blank**가 지정된 하이퍼링크에서 일어날 수 있는 문제입니다. + +> **target=\_blank**가 지정된 하이퍼링크에서 일어날 수 있는 문제입니다. `noopener` 속성은 클릭한 하이퍼링크를 통해 들어간 웹사이트측에서 원본 페이지의 제어를 할 수 없도록 막아주는 보안측면에서 중요한 역할을 합니다. 여기서 원본 페이지란, **A라는 하이퍼링크를 웹 페이지에서 제공하는 페이지**를 의미합니다. +
+ 만약 `noopener` 속성을 사용하지 않는다면, 새롭게 열린 페이지측에서 원본 페이지를 **자바스크립트를 사용하여 제어**할 수 있게 됩니다. (window.opener를 사용한 접근) + 이를 사용한 대표적인 피싱 공격이 **탭 내빙**이라고 하는 공격인데요, 아래 사진을 통해서 알아보겠습니다. -![탭 내빙 공격](https://github.com/yiyb0603/yiyb-blog/assets/50941453/3e413743-4449-4c75-9878-3d7e3b6d856a) +![탭 내빙 공격](/images/posts/html/noopener-and-noreferrer/tab-nabbing.png) 1. 사용자가 **cgm.example** 이라는 사이트에서 **happy.example**라는 사이트로 이동합니다. 2. 이때 **happy.example** 사이트에서 원본 페이지를 자신들이 만든 피싱용 사이트인 **cgn.example**로 변경합니다. @@ -69,30 +83,47 @@ export default MyComponent; 만약 여러분들이 위와같은 과정으로 개인정보를 탈취당하면 어떨것 같나요? 링크를 연결한 원본 페이지에 대해서 매우 심한 불만감을 느낄 수 있습니다. 이 문제는 하이퍼링크 태그에 **rel=noopener**를 지정하여 해결할 수 있습니다. +
-다행히도, 요즘 최신 브라우저들은 **target=_blank**로 지정된 하이퍼링크의 기본동작을 noopener로 정하는 추세인거 같아요. 그래도 프론트엔드 개발을 하시는 분이라면 주의해야합니다! + +다행히도, 요즘 최신 브라우저들은 **target=\_blank**로 지정된 하이퍼링크의 기본동작을 noopener로 정하는 추세인거 같아요. 그래도 프론트엔드 개발을 하시는 분이라면 주의해야합니다! ## 3. noreferrer 🚷 -> **target=_blank**가 지정된 하이퍼링크에서 일어날 수 있는 문제입니다. + +> **target=\_blank**가 지정된 하이퍼링크에서 일어날 수 있는 문제입니다. `noreferrer` 속성은 하이퍼링크를 통해 들어간 웹사이트측에서 **어떤 사이트에서 유입이 되었는지**를 숨길 수 있는 속성입니다. 기본적으로 A -> B 페이지로 하이퍼링크를 통해서 유입되는 경우 B 페이지에서는 A라는 페이지를 통해서 유입되었다는 정보를 알 수 있는데, `referer` 헤더로 확인이 가능합니다. +
-여기서 발생할 수 있는 문제점은 무엇일까요? 만약 여러분이 보안이 많이 허술한(?) 웹 페이지를 방문한다고 가정합시다. 해당 웹에서 로그인을 하고 여기저기 돌아다녀보니 한가지 놀라운점이 있었습니다. -그것은 바로, **URL에 개인정보를 담는 어메이징한 웹사이트**였기 때문이었죠. 그리고 여러분은 어떤 하이퍼링크를 클릭하여 다른 웹페이지로 넘어갔습니다. + +여기서 발생할 수 있는 문제점은 무엇일까요? 만약 여러분이 보안이 많이 허술한(?) 웹 페이지를 방문한다고 가정합시다. 해당 웹에서 로그인을 하고 여기저기 돌아다녀보니 한가지 놀라운점이 있었습니다. 그것은 바로, **URL에 개인정보를 담는 어메이징한 웹사이트**였기 때문이었죠. 그리고 여러분은 어떤 하이퍼링크를 클릭하여 다른 웹페이지로 넘어갔습니다. +
-이때 넘어간 웹 페이지에서는 HTTP 헤더인 `referer`를 통해서 여러분의 이전 URL을 얻게됩니다. 하필 개인정보를 URL에 담는 어메이징한 웹사이트였기에 개인정보까지 노출된 셈이 됩니다. 😥 + +이때 넘어간 웹 페이지에서는 HTTP 헤더인 `referer`를 통해서 여러분의 이전 URL을 얻게됩니다. 하필 개인정보를 URL에 담는 어메이징한 웹사이트였기에 개인정보까지 노출된 셈이 됩니다. +😥 +
+ 일반적인 URL을 `referer`로 보내는것은 큰 문제가 안되지만, 만약 이런 케이스의 보안을 챙기기 위해서는 `noreferrer` 속성을 지정하여 `referer`를 보내지 않을 수 있습니다. ## 4. nofollow ⛔ + 이번 글을 작성하다가 추가적으로 알게된 `nofollow`라는 속성에 대해서도 적어보겠습니다. +
+ 기존에 알려드렸던 `noopener`와 `noreferrer`는 보안적인 측면을 위해서 사용하는 속성이라면, `nofollow`는 검색엔진 최적화와 관련있습니다. 기본적으로 검색엔진 봇은 `a` 태그의 링크를 모두 탐색하는데요, 이때 **rel=nofollow**를 `a` 태그에 지정하면 해당 링크를 검색엔진 봇이 탐색하지 않습니다. +
+ 만약 여러분이 만드는 웹 서비스에 전혀 관계없는 링크가 있거나, 신뢰할 수 없는 링크일경우에 `nofollow` 속성을 걸어서 검색엔진에게 관계가 없음을 알려줄 수 있겠네요. ## 5. 마치며 📌 + 오늘은 HTML 하이퍼링크 태그에서 사용되는 `noopener`와 `noreferrer` 그리고 `nofollow` 속성을 알아보면서 각각 무엇인지 알아보았는데요, 여러분이 웹 서비스를 만들면서 보안 측면을 챙기실때 도움이 되었다면 좋겠습니다! 😃 +
-이상으로 글을 마치겠습니다. 긴 글 읽어주셔서 감사합니다! \ No newline at end of file + +이상으로 글을 마치겠습니다. 긴 글 읽어주셔서 감사합니다! diff --git a/posts/js-async-and-defer.mdx b/posts/JavaScript/js-async-and-defer.mdx similarity index 87% rename from posts/js-async-and-defer.mdx rename to posts/JavaScript/js-async-and-defer.mdx index af81d90..246a1fb 100644 --- a/posts/js-async-and-defer.mdx +++ b/posts/JavaScript/js-async-and-defer.mdx @@ -1,20 +1,24 @@ --- title: script 태그 로드방식, async와 defer 알아보기 description: 자바스크립트를 로드하는 방식인 async와 defer에 대해서 알아봅시다. -category: JavaScript createdAt: 2023-06-21 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/82bf1289-db47-41c7-8926-da5f51034802 +thumbnail: /images/posts/javascript/js-async-and-defer/thumbnail.png --- 안녕하세요! 오늘은 자바스크립트를 로드하는 기법중, 대표적으로 사용되는 **async**와 **defer** 방식이란 무엇이며, 어떻게 동작하는지 알아보겠습니다. 😃 ## 1. 일반적인 자바스크립트 로드 📕 -![자바스크립트 기본 로드](https://github.com/yiyb0603/yiyb-blog/assets/50941453/d75552a0-e286-4fa6-a52f-8d0f36ebef21) + +![자바스크립트 기본 로드](/images/posts/javascript/js-async-and-defer/original.png) 웹 페이지를 렌더링 과정중에서, 브라우저가 HTML을 파싱하는 과정이 있습니다. +
+ 이 HTML 파싱은 DOM Tree를 구성하기 위해서 필수로 거치게되는 과정인데요. HTML 문서에는 흔히 많이보던 `body`, `div`, `p` 등의 마크업 태그도 있지만, 프로젝트에서 자바스크립트를 사용하는 경우 **script** 태그는 필수적으로 들어가게 됩니다. +
+ script 태그안에는 직접 작성한 자바스크립트 코드 혹은 자바스크립트 코드가 위치한 파일, 주소의 정보를 저장할 수 있습니다. ```html @@ -25,53 +29,78 @@ script 태그안에는 직접 작성한 자바스크립트 코드 혹은 자바 alert('Hello World!'); - + ``` 브라우저가 script 태그를 마주하면 먼저 자바스크립트를 파싱해야 합니다. 하지만 **기본적으로 자바스크립트를 파싱하는 과정은 HTML 파싱 과정과 병렬적으로 실행이 불가능**합니다. + 그리고 자바스크립트의 파싱이 끝나면, 브라우저는 해당 자바스크립트는 바로 실행시키게 됩니다. 이동안 HTML 파싱은 중단이 되죠. +
+ 만약 HTML 파싱이 덜된상태에서 용량이 큰 자바스크립트를 파싱해야 하는 경우에는, 자바스크립트가 다운로드 되는동안 사용자는 렌더링된 페이지를 보는 시간이 늦어진다는 문제가 생기게 되죠. 😥 +
+ 이 문제를 해결하기 위해 등장한 JS 로드 방식이 바로 `async`와 `defer` 입니다! ## 2. async 로드 📒 + `async` 로드는 위에서 말했던것처럼, `HTML`과 `자바스크립트` 파싱을 병렬로 가능하도록 제공하는 로드 방식입니다. ```html 사용예시 - + ``` -![자바스크립트 async 로드](https://github.com/yiyb0603/yiyb-blog/assets/50941453/317cd895-535e-4838-a1a2-b5f72792cedd) +![자바스크립트 async 로드](/images/posts/javascript/js-async-and-defer/async.png) 그러나 `async`를 사용한 자바스크립트 로드는 밑에서 설명드릴 `defer`와 비교했을때 한가지 차이점이 있는데요, 만약 **파싱중이던 자바스크립트를 파싱 완료했을때, 곧바로 해당 자바스크립트를 실행 시킵니다.** 그리고 자바스크립트의 실행이 끝나기 전까지는 HTML 파싱이 중단되죠. +
+ 만약 실행되는 자바스크립트가 용량이 큰 소스라면, 아직 남은 HTML의 파싱 대기시간이 늘어나게 될 수 있죠. > `async`와 `defer` 두개 모두 **JS 파싱/다운로드**를 HTML과 병렬적으로 할 수는 있지만, **자바스크립트가 실행되는 동안에는 병렬적으로 HTML을 파싱하지 못한다는 특징**이 있습니다. 일반적인 자바스크립트 로드 동작 과정이랑 비교했을때, **HTML과 JS를 병렬적으로 파싱** 하는 특징을 제외하고는 같은 특징을 지닙니다. +
+ 즉, 순서보장이 필요한 작업의 경우에는 `async` 로드 방식을 사용을 지양해야 합니다. 만약 여러 개의 script 태그에 async 로드를 지정하면, script 태그의 순서와는 상관없이 파싱이 완료된 자바스크립트부터 먼저 실행되므로 순서가 보장되지 않습니다. ## 3. defer 로드 📘 + `defer` 로드는 `HTML`과 `자바스크립트` 파싱을 병렬로 가능하도록 제공하는 로드 방식입니다. 그러나 위에서 설명드린 `async` 로드와 약간의 차이점이 있습니다. ```html 사용예시 - + ``` -![자바스크립트 defer 로드](https://github.com/yiyb0603/yiyb-blog/assets/50941453/2949bac2-d1bf-46ad-a3a5-85f8adef5cca) + +![자바스크립트 defer 로드](/images/posts/javascript/js-async-and-defer/defer.png) 위 사진을 통해서 눈치채신 분들도 있겠지만, `defer` 로드 또한 **HTML과 JS 파싱/ 다운로드**를 병렬적으로 진행하는데, 이후 **HTML 파싱과정이 끝나면 파싱되었던 자바스크립트를 실행**한다라는 특징이 있습니다. +
+ `async` 로드 방식과 비교했을때 `defer` 로드는 자바스크립트의 실행 순서를 보장하며, **HTML 파싱이 끝나고나서 실행된다는 특징**이 있으므로 DOM을 접근하는 자바스크립트 코드에 대해서 오류가 발생하지 않게되죠. 👍 ## 4. 마치며 📌 + 오늘은 자바스크립트의 기본 로드 방식과 `async`, `defer`를 사용한 자바스크립트 로드에 대해서 알아봤습니다. +
+ 예전에 제가 Next.js에서 `script` 태그를 사용할때 `async` 혹은 `defer` 로드 지정방식이 필수였던 경험이 있어서 두 방식의 차이점에 대해서 예전에 알아보았는데, 오늘 이렇게 적을 수 있었네요. 👍 +
-오늘의 글은 여기서 마무리 하겠습니다. 긴 글 읽어주셔서 감사합니다! 😃 \ No newline at end of file + +오늘의 글은 여기서 마무리 하겠습니다. 긴 글 읽어주셔서 감사합니다! 😃 diff --git a/posts/2023-retrospect.mdx b/posts/Life/2023-retrospect.mdx similarity index 90% rename from posts/2023-retrospect.mdx rename to posts/Life/2023-retrospect.mdx index ffd8735..583ae48 100644 --- a/posts/2023-retrospect.mdx +++ b/posts/Life/2023-retrospect.mdx @@ -1,9 +1,8 @@ --- title: 어느 한 3년차 프론트엔드 개발자의 2023년 회고 description: 2023년 회고를 통해서 주요 일들을 정리합니다. -category: Life createdAt: 2024-01-11 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/4e92a834-2ad7-4e41-b06c-f8be24514f6f +thumbnail: /images/posts/life/2023-retrospect/thumbnail.png --- 안녕하세요! 얼마전에 2024년 새해가 밝았습니다. 이 글을 읽으시는분들은 새해복 많이 받으세요. 🙇 @@ -26,7 +25,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/4e92a834-2ad7-4 그래서 회사를 다니면서 군복무 기간을 대신할 수 있는 [산업기능요원](https://www.mma.go.kr/contents.do?mc=mma0000760) 신분을 얻을 수 있는데요. **현역 TO의 경우에는 보충역 TO와 비교했을때 경쟁률이 매우 높고, 수량도 적게 배치가 됩니다.** 회사에서 같이 일하는 개발자분도 당시에 현역 TO 배정을 기다리고 있었어서, 2개의 TO가 나와야 제가 배정받을 수 있었습니다. -![산업기능요원 배정 완료](https://github.com/yiyb0603/yiyb-blog/assets/50941453/dea7d8f9-2ed0-4e5a-b5e8-4e5abbb78a87) +![산업기능요원 배정 완료](/images/posts/life/2023-retrospect/industrial-technical-personnel.png) 계속 병무청 사이트를 새로고침하며 산업기능요원 TO 배정 결과를 기다리다가, 결과일이 되어서 확인해보니 정말 운이 좋게도 현역 TO를 2개 배정받을 수 있었습니다. 😀 @@ -36,7 +35,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/4e92a834-2ad7-4 ## 2. 개인 블로그 제작 🔨 -![yiyb-blog](https://github.com/yiyb0603/yiyb-blog/assets/50941453/512fb682-99c7-4bf0-a37f-bd945b0e6988) +![yiyb-blog](/images/posts/life/2023-retrospect/yiyb-blog.png) 2023년 2월경에 [contentlayer](https://contentlayer.dev)라는 라이브러리를 알고나서, 퇴근후에 주말내내 개인 블로그를 만드는데 집중했던 시간들이 기억나네요. 😀 @@ -54,7 +53,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/4e92a834-2ad7-4 블로그라는 플랫폼은 검색엔진에서 잘 노출되면 유입되는 사용자가 늘고, 홍보가 될 수 있기 때문에 `검색엔진 최적화` 부분에 대해서 많이 신경을 썼던것 같습니다. 그리고 블로그가 `Next.js` 코드 기반으로 만들어졌기에 검색엔진 최적화 부분에서 유리하겠다고 생각했습니다. -![블로그 검색엔진 최적화 실적](https://github.com/yiyb0603/yiyb-blog/assets/50941453/b2c536b7-c4a8-4b60-8d12-e23b23992298) +![블로그 검색엔진 최적화 실적](/images/posts/life/2023-retrospect/search-console.png) 검색엔진 최적화를 위해서 아래의 방법들을 사용했습니다. @@ -68,9 +67,9 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/4e92a834-2ad7-4 그래도 일부 글들은 특정 검색어에 대해 최상단에 뜨기도 해서 만족합니다. -![react-query 글 검색결과](https://github.com/yiyb0603/yiyb-blog/assets/50941453/0e74a7fb-d6c0-412e-a38e-ff0da46b3d86) +![react-query 글 검색결과](/images/posts/life/2023-retrospect/react-query-google-search.png) -![Next.js contentlayer 글 검색결과](https://github.com/yiyb0603/yiyb-blog/assets/50941453/2b0cac7e-3e77-4224-b2b2-41a9bc1b8b0e) +![Next.js contentlayer 글 검색결과](/images/posts/life/2023-retrospect/nextjs-contentlayer-google-search.png) 최근에 작성한 글도 위처럼 잘 수집되어서 얼른 검색결과에 뜨면 좋겠습니다. 🙏 @@ -106,7 +105,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/4e92a834-2ad7-4 기존에는 웹 서버를 `네이버 클라우드 플랫폼`을 사용했는데요. 이 플랫폼은 기본적으로 서버비가 많이 나오는것이 좀 거슬렸었는데요. 네이버 클라우드 플랫폼을 대신해서 사용할 호스팅 서버를 알아보다가 **웹 서버 관리가 쉽고, 시스템 성능도 좋고 저렴한** `Vercel`을 사용하기로 개발팀내에서 결정했습니다. -![Vercel](https://github.com/yiyb0603/yiyb-blog/assets/50941453/47b3a48e-22c2-4ced-a2ba-72513d683840) +![Vercel](/images/posts/life/2023-retrospect/vercel.png) Vercel에서 제공하는 [CLI](https://vercel.com/docs/cli)를 사용하여 CI툴인 `GitHub Actions`를 통해 배포 과정을 완전히 자동화 하여 배포 과정에 대해서 시간을 절감할 수 있었습니다. @@ -120,7 +119,7 @@ Vercel에서 제공하는 [CLI](https://vercel.com/docs/cli)를 사용하여 CI 2023년 8월, `기초군사훈련`을 끝내고 돌아온 후, 곧바로 [토스](https://toss.im)앱을 서비스 하고있는 [비바리퍼블리카](https://toss.im/team) 회사에 들어가고 싶다는 생각에 서류를 제출하여 이직 시도를 했습니다. -![토스 서류접수 완료](https://github.com/yiyb0603/yiyb-blog/assets/50941453/be2d2840-8aae-4946-9c0e-35350659896b) +![토스 서류접수 완료](/images/posts/life/2023-retrospect/toss-portfolio-submit.png) 이직을 하려는 이유는 아래와 같았습니다. @@ -141,13 +140,13 @@ Vercel에서 제공하는 [CLI](https://vercel.com/docs/cli)를 사용하여 CI > 채용 프로세스 - **Frontend Developer (산업기능요원) 기준** > 서류전형 -> 과제전형 -> 직무인터뷰 -> 컬쳐핏 인터뷰 -> 처우협의 -![토스 서류전형 합격](https://github.com/yiyb0603/yiyb-blog/assets/50941453/4011c7f0-3d90-405b-bc28-b188a218352d) +![토스 서류전형 합격](/images/posts/life/2023-retrospect/toss-portfolio-pass.png) 그렇게 `서류전형`부터 시작을 했었는데요. 담당자분께서 제 서류를 좋게 봐주셨는지 **서류전형은 통과**하고, 곧바로 과제전형 진행 일정을 잡아서 과제를 진행했습니다. 당시 평일에 `과제전형` 일정이 잡혀있어서 오후반차를 사용했는데, 개인적인 중요한 일이 있다고 동료분에게 둘러댔던 경험이.. 있네요 😅 -![토스 과제전형 합격](https://github.com/yiyb0603/yiyb-blog/assets/50941453/6fc2c7b3-1f06-497c-b430-494625d71451) +![토스 과제전형 합격](/images/posts/life/2023-retrospect/toss-homework-pass.png) 그렇게 `과제전형`이 끝나고, 제 코드를 담당자분들이 좋게 봐주실지 예측이 안되어서 확신이 들지 않았는데요. 담당자분들이 제 코드를 좋게 봐주셨는지 다행히도 2주정도 뒤에 **과제전형 합격 결과**를 들을 수 있었습니다. 😀 @@ -159,7 +158,7 @@ Vercel에서 제공하는 [CLI](https://vercel.com/docs/cli)를 사용하여 CI 그렇게 직무인터뷰를 진행하였고, 인터뷰전까지 예상 질문에 대해서 많이 연습한덕인지 질문에 대해서는 답을 주저없이 잘 이야기 할 수 있었습니다. 그러나 제가 답한 내용들에 대해서 담당자분들이 어떻게 생각하셨는지 모르겠어서 예측이 되진 않았습니다. -![토스 직무인터뷰 합격](https://github.com/yiyb0603/yiyb-blog/assets/50941453/1b169445-fe80-4097-b2de-305681fa765b) +![토스 직무인터뷰 합격](/images/posts/life/2023-retrospect/toss-technical-interview-pass.png) 며칠이 지나고, 담당자분들이 제 답변을 좋게 봐주셨는지 **직무인터뷰 합격 결과**를 얻을 수 있었습니다. @@ -171,7 +170,7 @@ Vercel에서 제공하는 [CLI](https://vercel.com/docs/cli)를 사용하여 CI 그렇게 컬쳐핏 인터뷰를 진행하였습니다. 전체적으로 커피 한잔 하면서 이야기를 나누는 느낌이였으며, 질문주시는 내용들에 대해 저의 생각과 의견을 그대로 답했습니다. 토스에서는 누군가를 채용하려고 할때 매우 신중하게 고민하고, 생각하는 것 같았습니다. -![image](https://github.com/yiyb0603/yiyb-blog/assets/50941453/bc6f2ce8-9117-4c79-bf39-d2e996f69911) +![image](/images/posts/life/2023-retrospect/toss-failed.png) 인터뷰가 끝나고 하루하루 결과를 기다리며 지내다가 메일 한통이 도착했고, 아쉽게도 `최종 탈락`이라는 결과를 전달 받았습니다. 🥲 면접관님이 질문 주신 항목에 대해서 좀 성급하게 답한 항목들이 있는것 같고, 제가 답한 항목에 대해서 면접관님이 토스와는 맞지 않다는 생각이셨던 것 같아요. @@ -207,7 +206,7 @@ try { 이 일은 올해(2024년 1월초)에 있었던 일이지만, 간단하게 적어보려고 합니다. (나중에 새 블로그 글로 올릴것 같아요.) -![경북소프트웨어고등학교 강의](https://github.com/yiyb0603/yiyb-blog/assets/50941453/3253512c-7774-4071-9694-c7d0a8c029e0) +![경북소프트웨어고등학교 강의](/images/posts/life/2023-retrospect/lecture.jpeg) 2024년 1월 초에 총 5일의 기간동안 `경북소프트웨어고등학교`에서 1학년 학생들을 대상으로 한 **해커톤 대비 웹 프로그래밍 특강 강사**를 했었습니다. diff --git a/posts/basic-military-training.mdx b/posts/Life/basic-military-training.mdx similarity index 67% rename from posts/basic-military-training.mdx rename to posts/Life/basic-military-training.mdx index 79392e4..ddf4673 100644 --- a/posts/basic-military-training.mdx +++ b/posts/Life/basic-military-training.mdx @@ -1,73 +1,94 @@ --- title: 산업기능요원 기초군사훈련 3주 후기 description: 지난 7월, 3주의 기간동안 산업기능요원 기초군사훈련을 갔다온 후기입니다. -category: Life createdAt: 2023-08-15 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/314bef4f-81a9-4be0-b563-1b59788fff53 +thumbnail: /images/posts/life/basic-military-training/thumbnail.png --- 안녕하세요! 오늘은 지난 7월에 3주의 기간(2023.07.10 ~ 07.28) 동안 **산업기능요원 기초군사훈련**을 갔다온 후기를 작성해보는 시간을 가져보겠습니다. ## 1. 기초군사훈련이란? 🤔 + 기초군사훈련은 군인들에게 **기본적인 군사 지식과 군사 생활에 필요한 기술을 가르치는 군사 교육과정**을 말합니다. 군대 내에서 군인으로서의 역할과 책임을 이해하고 이행할 수 있도록 준비하는 것을 목표로 하며, 국방력 유지 및 국가 안전을 위해 중요한 역할을 합니다. +
+ 현역으로 입대하는 경우 `5주`의 기간동안 훈련을 받지만, 저는 산업기능요원이기 때문에 보충역 훈련소로 입소하여 `3주`의 기간동안 훈련을 받습니다. ## 2. 50사단 신병교육대대 🏷️ -![50사단 신병교육대대](https://github.com/yiyb0603/yiyb-blog/assets/50941453/871e255a-abbc-496e-8c7a-37acfd413146) + +![50사단 신병교육대대](/images/posts/life/basic-military-training/daegu-50-division.png) 저는 대구에서 산업기능요원으로 근무를 하고있으므로, 대구·경북의 보충역 훈련소인 **50사단 신병교육대대**에 입소하게 되었습니다. 명칭은 `강철부대`이며 현재는 대구·경북을 수호를 목적으로 활동하는 부대입니다. +
+ 아무래도 대구의 맨 위에 위치하고 있어서 대부분 차를타고 오셨는데, 다행히도 저는 본가가 버스로 약 15분 거리에 있기때문에 교통에 대한 문제는 없이 편하게 갈 수 있었습니다. ## 3. 일일 생활 시간표 📆 + 제가 기초군사훈련 기간동안의 일일 생활 시간표입니다. > 2023년 7월 여름 기준이며, 시간에 오차가 있을 수 있습니다. -|시간|활동|비고| -|---|---|---| -|06:30 ~ 06:50|아침 기상 스트레칭 및 점호 준비|전투복 착용| -|06:50 ~ 07:30|아침 점호|인원보고, 도수체조 등 (뜀걸음 선택) | -|07:30 ~ 09:00|아침식사 및 세면|-| -|09:00 ~ 11:40|오전 교육|40분 훈련, 10분간 휴식| -|11:40 ~ 13:00|점심식사|-| -|13:00 ~ 17:30|오후 교육|40분 훈련, 10분간 휴식| -|17:30 ~ 19:00|저녁식사|-| -|19:00 ~ 20:50|자유시간|때때로 추가교육이 있거나 물품 배부| -|20:50 ~ 21:30|담당구역 청소 및 저녁점호 준비|-| -|21:30 ~ 21:50|저녁점호|전투복 착용| -|22:00 ~ 06:30|취침|불침번은 1시간씩 교대| +| 시간 | 활동 | 비고 | +| ------------- | ------------------------------- | ----------------------------------- | +| 06:30 ~ 06:50 | 아침 기상 스트레칭 및 점호 준비 | 전투복 착용 | +| 06:50 ~ 07:30 | 아침 점호 | 인원보고, 도수체조 등 (뜀걸음 선택) | +| 07:30 ~ 09:00 | 아침식사 및 세면 | - | +| 09:00 ~ 11:40 | 오전 교육 | 40분 훈련, 10분간 휴식 | +| 11:40 ~ 13:00 | 점심식사 | - | +| 13:00 ~ 17:30 | 오후 교육 | 40분 훈련, 10분간 휴식 | +| 17:30 ~ 19:00 | 저녁식사 | - | +| 19:00 ~ 20:50 | 자유시간 | 때때로 추가교육이 있거나 물품 배부 | +| 20:50 ~ 21:30 | 담당구역 청소 및 저녁점호 준비 | - | +| 21:30 ~ 21:50 | 저녁점호 | 전투복 착용 | +| 22:00 ~ 06:30 | 취침 | 불침번은 1시간씩 교대 | ## 4. 주차별 교육 📒 -|주차|훈련|비고| -|---|---|---| -|1주차 (1)|기초 제식훈련 (경례, 좌/우향우, 바른걸음, 총기제식)|우천시 실내교육| -|1주차 (2)|정신교육 (국가와 군대, 군인의 사명 등등)|실내 프레젠테이션 교육 -|2주차|사격훈련, 화생방 훈련, 수류탄 투척 훈련|우천시 실내교육, 화생방 실습 선택| -|3주차 (1)|각개전투, 행군|완전군장/단독군장/환자조 선택| -|3주차 (2)|수료식 연습 및 수료식(군사훈련 종료)|-| + +| 주차 | 훈련 | 비고 | +| --------- | --------------------------------------------------- | --------------------------------- | +| 1주차 (1) | 기초 제식훈련 (경례, 좌/우향우, 바른걸음, 총기제식) | 우천시 실내교육 | +| 1주차 (2) | 정신교육 (국가와 군대, 군인의 사명 등등) | 실내 프레젠테이션 교육 | +| 2주차 | 사격훈련, 화생방 훈련, 수류탄 투척 훈련 | 우천시 실내교육, 화생방 실습 선택 | +| 3주차 (1) | 각개전투, 행군 | 완전군장/단독군장/환자조 선택 | +| 3주차 (2) | 수료식 연습 및 수료식(군사훈련 종료) | - | ## 5. 훈련 후기 🫡 + 1주차에는 군인으로서의 기본 동작, 자세, 마음가짐을 배우는 시간이였기 때문에 훈련은 어렵지 않았습니다. 여담으로 1주차는 원래 하루종일 격리 생활을 하는줄 알았는데, 요즘 코로나가 잠잠해져서 격리는 하지않고 훈련을 했네요. +
+ 2주차에는 화생방 실습을 할까말까 고민하고 있었는데, **부대에서 코로나 확진자가 다수 나오는바람**에 화생방은 그냥 이론수업만 하는식으로 진행되어 **가스 실습은 생략**되었습니다. + 이후에 저도 코로나 확진을 받아서 약 5일동안 다른 중대 생활관에서 격리되었습니다 😥 사회에서도 걸려보지 않은 코로나를 여기서 걸리니 참 신기하기도 했습니다. 코로나 격리로 인해서 저는 `각개전투`와 `수류탄 투척` 훈련을 받지 못했습니다. +
-3주차에는 격리가 해제된후, `주간 행군`을 했습니다. 원래 `단독 군장` 행군을 신청했었는데, 제가 격리해제된지 얼마 되지 않아서 `환자조` 행군으로 강제로 배치되었습니다. -환자조는 정말 환자들을 위한 행군 훈련이라고 생각하면 됩니다. 운동장을 `단독 군장` 상태로 하루종일 돌고, 쉬는시간도 많이 줍니다 하하 😂 + +3주차에는 격리가 해제된후, `주간 행군`을 했습니다. 원래 `단독 군장` 행군을 신청했었는데, 제가 격리해제된지 얼마 되지 않아서 `환자조` 행군으로 강제로 배치되었습니다. 환자조는 + +정말 환자들을 위한 행군 훈련이라고 생각하면 됩니다. 운동장을 `단독 군장` 상태로 하루종일 돌고, 쉬는시간도 많이 줍니다 하하 😂 +
+ 군사훈련의 메인이라고 할 수있는 훈련들을 코로나로 인해 못하게 되어서 아쉬움도 있습니다. ## 6. 생활관 생활 🏠 + 맨 처음 생활관에 들어온 후, 저를 제외한 13명의 낯선 사람들과 생활을 잘할 수 있을까? 하는 생각에 막막하기도 했지만 1주차가 끝날쯔음에는 서로 편하게 다가가서 이런저런 이야기도 나누며 친해질 수 있었습니다. +
+ 나이대는 00년생 ~ 04년생 까지 있었으며, 다른 직종의 산업기능요원 훈련병들과 이야기를 나누면서 공감대를 형성하기도 했습니다. 3주간의 훈련이 끝난 지금도 단톡방에서 이야기하면서 모임도 가지곤 했습니다. 😃 ## 7. 챙겨가야 하는 물품 🛒 + > 필수적으로 **신분증**과 **나라사랑카드**는 꼭 들고가야 합니다. 기본적으로 생활에 필요한 물품들은 부대에서 지급이 됩니다. (수건, 양말, 속옷, 치약/칫솔, 비누, 휴지 등등) + 하지만 수량이 제한되어 있기도 하고, 일부는 미지급되는 물품도 있기 때문에 아래의 물품은 가져가는것을 추천합니다. - **시계** (야외 훈련중 시간이 궁금할때가 많습니다. 가져가는것을 추천) @@ -77,10 +98,15 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/314bef4f-81a9-4 이외에도 **예비군 훈련때 입어야하는 A급 전투복, 방상외피** 등도 지급되는데, 이를 포함하여 **부대에서 지급된 모든 물품은 퇴소시 모두 가져가야 합니다.** (비누나 구두약처럼 부피가 작은 물품은 몰래 버려도 괜찮습니다. 😂) 일반적인 배낭에 담기에는 공간이 많이 부족하므로, `캐리어`를 꼭! 가져가는것이 퇴소할때 매우 편합니다. +
+ 추가적으로, PX에서 **물건을 구매할 의향이 있다면 돈을 넉넉하게 충전**하세요. (가격은 사회에서 판매하는 가격보다 훨씬 저렴합니다.) ## 8. 마치며 📌 + 지금까지 3주간의 기초군사훈련 후기를 작성해보았는데요, 작성하는동안 훈련소에서 있었던 일들과 훈련들이 계속 리마인드 되었던거 같습니다. 😂 보충역 훈련소를 가시는 모든분들께 도움이 되었으면 좋겠습니다. +
-이상으로 글을 마치겠습니다. 감사합니다! 😃 \ No newline at end of file + +이상으로 글을 마치겠습니다. 감사합니다! 😃 diff --git a/posts/feels-of-live-alone-two-months.mdx b/posts/Life/feels-of-live-alone-two-months.mdx similarity index 88% rename from posts/feels-of-live-alone-two-months.mdx rename to posts/Life/feels-of-live-alone-two-months.mdx index 8c91289..de1d01b 100644 --- a/posts/feels-of-live-alone-two-months.mdx +++ b/posts/Life/feels-of-live-alone-two-months.mdx @@ -1,29 +1,42 @@ --- title: 자취생활 두달차, 느낀점을 정리해보자. description: 두달동안 자취생활을 하면서 이것저것 느낀점들 및 생각을 정리한 글입니다. -category: Life createdAt: 2023-04-29 -thumbnail: https://user-images.githubusercontent.com/50941453/235279835-216b3d76-38b7-465d-a16b-63a6ae50ac1b.png +thumbnail: /images/posts/life/feels-of-live-alone-two-months/thumbnail.png --- 안녕하세요! 오늘은 간만에 개발관련 글이 아닌, 일상 이야기를 처음으로 적어보려고 합니다. 😃 그동안 자주 일상 이야기도 적어야지.. 생각했었는데, 계속 개발 이야기만 적게되더라고요 😂 그래서 오늘은 **자취**를 주제로 이야기를 적어보려고 해요. ## 1. 왜 자취를 하게 되었는가? 🤔 -![정말 힘든 출퇴근](https://user-images.githubusercontent.com/50941453/235279601-31d0f7de-491d-4e97-9c25-a8a882f125e7.png) + +![정말 힘든 출퇴근](/images/posts/life/feels-of-live-alone-two-months/tired.png) + 저는 자취를 하기 전까지 **약 1년 6개월**동안 집에서 회사까지 출퇴근을하며 살아왔습니다. 하지만 가는데 1시간, 오는데 1시간정도가 소요되는 거리이다보니 매번 아침일찍 일어나야 했고.. 지하철 타이밍도 문제 없게끔 맞춰야했고.. 언제나 한픽셀 한픽셀씩 꼭 맞춰나가야 제가 원하는 시간에 출근을 할 수 있었습니다. 매번 잠에서 덜깬 컨디션으로 지하철🚇을 환승해나갔죠. +
+ 그러나 사실 저희 회사는 출근이 5분 늦던, 10분 늦던 **늦은만큼 일을 더하면 된다.**라는 지침이였기에 기존까지는 지각에 대해서 큰 부담이 없었으나, **최근에 제가 산업기능요원에 편입 및 복무를 시작하게 되면서** 지각에 대한 규정이 엄격해졌습니다. (지각한 시간이 일부 누적되면 연차에서 깎이는 형태) +
+ 저는 평소에도 거리가 먼 출퇴근길에 대해서 생각이 많았는데, 때마침 제가 원하던 **산업기능요원 편입** 성공도 했겠다, 모아둔 돈도 있겠다, 자취를 시작해보자! 하고 결심하게 되었습니다. ## 2. 집 구하는 과정 📒 -![집을 구하기 위한 돈쓰기](https://user-images.githubusercontent.com/50941453/235279654-3958aa05-7e50-4436-8005-c885aeadfe35.png) + +![집을 구하기 위한 돈쓰기](/images/posts/life/feels-of-live-alone-two-months/think-money-spend.png) + 그렇게 저는 **회사와 최대한 가까운 위치의 집**을 목표로 집을 찾아보게 되었습니다. 그저 처음에는 **월세 오피스텔**을 알아보게 되었는데 월세라는 돈은 매달 큰 부담이 되기도하며 그저 버리는 돈 이라고 생각을 했기에 월세는 더이상 알아보지 않았습니다. +
+ 두번째로는 전세를 알아보았습니다. 아 그리고, 처음에 저는 **전세 = 은행한테 평생 쫓겨다님** 이라는 인식이 엄청 강해서 부정적이었는데, 자세히 알아보니까 그게 아니더라고요? 전세사기만 안당하면 전세가 정말 킹가성비임을 알게되어 곧바로 전세를 알아보았습니다. +
+ 그렇게 이것저것 알아본결과, **회사 근처(도보 5 ~ 10분)에 있는 한 오피스텔 방**을 전세로 **9,000만원**에 계약할 수 있었습니다. 그런데 전세 계약에 필요한 돈은 어디서 났냐고요? -
+ +
+ 바로 **[중소기업 취업청년 전월세 보증금 대출](https://nhuf.molit.go.kr/FP/FP05/FP0502/FP05020603.jsp)** 제도를 통해서 얻을 수 있었습니다. 조금 아슬아슬하게 조건에 만족해서 다행이네요. 😃 대출을 받을 수 있는 조건은 아래와 같습니다. @@ -31,13 +44,15 @@ thumbnail: https://user-images.githubusercontent.com/50941453/235279835-216b3d76 - 만 19세 이상 ~ 만 34세 이하 청년 - 개인 연소득 3,500만원 미만 - 중소, 중견기업 재직자 또는 중소기업진흥공단, 신용보증기금 및 기술보증기금의 청년창업 지원을 받고 있는 자 -
+ 그렇게 저는 **대출금 7,200만원 + 개인 자산 1,800만원 = 총 9,000만원**에 계약을 할 수 있었습니다. 그 외 비용(부동산 지출, 대출제부대비용, 자취물품 구입 등등)은 다 합쳐서 **100만원** 정도 사용한거 같네요. -![간단하게 찍어봤던 자취방 사진](https://user-images.githubusercontent.com/50941453/235278251-d155c770-ebdb-4481-b059-f3411963c216.jpg) +![간단하게 찍어봤던 자취방 사진](/images/posts/life/feels-of-live-alone-two-months/home.jpeg) + > 간단하게 찍어봤던 자취방 사진 ### 2-1. 그외 집 정보 ℹ️ + - 위치: 동대구역에서 도보 5분거리 - 층수: 전체 23층, 본인 19층 - 평수: 8평 @@ -46,10 +61,15 @@ thumbnail: https://user-images.githubusercontent.com/50941453/235279835-216b3d76 - 기본 시설: 천장형 에어컨, 붙박이식 옷장 및 냉장고, 전기레인지 (별칭을 모르겠다), 세탁기 등등 ## 3. 자취생활 및 느낀점 💡 + 저는 3월 초부터 자취를 시작하게되었는데요, 기본적으로 **집에서 음식은 먹지 않습니다.** 🦐 요리를 할경우엔 이것저것 뒷정리가 많아질것 같아서 안했고.. 시켜먹거나 하면 쓰레기 치우는게 너무 귀찮을것 같아서 아예 집에선 음식을 먹지 않았습니다. + 그냥 자취방에서는 자고, 씻고, 노트북하고 밖에 안했던거 같네요. 😂 +
+ 그래도 지금까지 약 한달반 ~ 두달동안 살아본 결과 저의 생각은 아래와 같습니다. + - 일반 쓰레기를 봉투 끝까지 채우는것은 인내심을 유발한다. - 머리카락이 바닥에 떨어지지 않도록 막는 방법은 존재하지 않을것이다. - 보일러, 환풍기는 항상 끄고 나가자. @@ -57,11 +77,14 @@ thumbnail: https://user-images.githubusercontent.com/50941453/235279835-216b3d76 - 쿠팡의 효율이 극대화 될 수 있다. - 디코같은거 할때 큰소리로 말할 수 있다. - 그래도 집에 혼자밖에 없다보니까 집중이 매우 잘된다. -- 다음 자취집을 구하게 되면 회사와의 거리를 최우선으로 생각하자. -(엄청 가까우니까 너무 편하다. 시간과 체력을 아낄 수 있음) +- 다음 자취집을 구하게 되면 회사와의 거리를 최우선으로 생각하자. (엄청 가까우니까 너무 편하다. 시간과 체력을 아낄 수 있음) ## 4. 마치며 📌 + 여러분들도 만약 자취를 하고싶다 라는 생각이 들면, 충분한 고민 후에 결정하시면 좋을것 같습니다. 어떤사람에겐 엄청 좋을수 있으나, 반대로 어떤사람에겐 가족과 지내는것이 더 편할 수 있겠다라는 생각이 드네요. + 저의 경우에는 처음엔 가족들이 보고싶고, 외롭고, 심심했는데 점차 적응해보니까 편해진것 같습니다. 😃 +
-아무쪼록 긴 글 읽어주셔서 감사합니다! \ No newline at end of file + +아무쪼록 긴 글 읽어주셔서 감사합니다! diff --git a/posts/gbsw-frontend-lecture.mdx b/posts/Life/gbsw-frontend-lecture.mdx similarity index 91% rename from posts/gbsw-frontend-lecture.mdx rename to posts/Life/gbsw-frontend-lecture.mdx index b7b1c66..c41da81 100644 --- a/posts/gbsw-frontend-lecture.mdx +++ b/posts/Life/gbsw-frontend-lecture.mdx @@ -1,9 +1,8 @@ --- title: 경북소프트웨어고등학교 프론트엔드 강의 후기 description: 5일동안 경북소프트웨어고등학교에서 1학년 대상으로 프론트엔드 강의를 진행한 후기를 정리합니다. -category: Life createdAt: 2024-01-14 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/9523c80b-6670-4307-a91f-e78a875e4dc4 +thumbnail: /images/posts/life/gbsw-frontend-lecture/thumbnail.png --- 안녕하세요! 오늘은 지난 5일동안 제가 `경북소프트웨어고등학교`에서 **프론트엔드 강의**를 진행한 일에 대해서 후기를 정리해보도록 하겠습니다. 😀 @@ -20,7 +19,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/9523c80b-6670-4 ### 1-1. 경북소프트웨어고등학교란? -![경북소프트웨어고등학교 모습](https://github.com/yiyb0603/yiyb-blog/assets/50941453/916d0439-bec4-4e81-9ba0-9c85085eeb03) +![경북소프트웨어고등학교 모습](/images/posts/life/gbsw-frontend-lecture/gbsw-panorama.png) 경북소프트웨어고등학교는 `경상북도 의성군 봉양면`에 위치한 **기숙형 공립 소프트웨어(공업계열) 특성화 고등학교**입니다. 현재는 특성화 고등학교지만 **내년(2025년)에는 마이스터고로 개교**된다고 하네요! @@ -58,7 +57,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/9523c80b-6670-4 그렇게 2024년 1월 2일이 되었고, 당일 아침부터 과목별로 반을 나누어서 강의를 진행했습니다. -![경북소프트웨어고등학교 강의](https://github.com/yiyb0603/yiyb-blog/assets/50941453/3253512c-7774-4071-9694-c7d0a8c029e0) +![경북소프트웨어고등학교 강의](/images/posts/life/gbsw-frontend-lecture/lecture.jpeg) ### 3-1. 1일차 @@ -78,7 +77,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/9523c80b-6670-4 ### 3-2. 2일차 -![JavaScript 배열 메소드](https://media.geeksforgeeks.org/wp-content/cdn-uploads/20220614183844/Best-known-ARRAY-methods.png) +![JavaScript 배열 메소드](/images/posts/life/gbsw-frontend-lecture/js-array-methods.png) 2일차 오전 시간에는 JavaScript 프로그래밍에서 자주 사용되는 개념 중 하나인 `배열 메소드`를 알아보고, 실습해보는 시간을 가졌습니다. @@ -86,7 +85,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/9523c80b-6670-4 1일차까지 제법 쉬운 개념들을 위주로 배우다가 2일차부터 심화적인 `배열 메소드`를 배우다보니 학생들이 좀 어려워하는 모습들이 보이더라고요. 저 또한 `배열 메소드`를 학생때 처음 배울때 헷갈렸던 경험이 있었기 때문에, **최대한 이해하기 쉽도록 설명**(배열 메소드의 실제 생김새) 및 **다양한 예제**(특정한 조건을 만족하는 배열 반환하기 등)를 풀어보며 익혀보는 시간을 오전에 계속 가졌습니다. -![React.js](https://cdn.inflearn.com/wp-content/uploads/react.png) +![React.js](/images/posts/life/gbsw-frontend-lecture/react.png) 원래는 오후 시간에 바닐라 자바스크립트를 사용해서 실습해보는 시간을 가지려고 했으나 다음주에 있을 해커톤 대비용 특강이기도 했고, 많은 학생들이 `React`를 빠르게 배워보고 싶다는 의견이 있어서 곧바로 React를 알아보는 시간을 가졌습니다. @@ -96,7 +95,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/9523c80b-6670-4 ### 3-3. 3일차 -![React Hooks](https://images.velog.io/images/gwanuuoo/post/bc6181a2-d2c8-440e-8c25-d776d410432e/1_-Ijet6kVJqGgul6adezDLQ.png) +![React Hooks](/images/posts/life/gbsw-frontend-lecture/react-hooks.png) 3일차 오전 시간에는 `React`에서 필수적으로 알아야하는 `Hooks`에 대해서 알아보았습니다. @@ -104,7 +103,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/9523c80b-6670-4 컴포넌트의 상태를 관리하여 리렌더링을 발생시키는 `useState`부터 메모이제이션 역할을 하는 `useCallback`과 `useMemo`, 컴포넌트 생명주기에 따라서 작업을 수행하는 `useEffect`, 특정 DOM 요소에 접근하거나 리렌더링이 필요하지 않은 상태를 업데이트하는 `useRef` 까지 **필수적인 React Hooks**들을 알아보는 시간을 가졌습니다. -![Todo List](https://github.com/yiyb0603/yiyb-blog/assets/50941453/d3b806bb-8e47-4953-a7f6-278b3ec9ef35) +![Todo List](/images/posts/life/gbsw-frontend-lecture/todo-list.png) 오후 시간에는 지금까지 배운 `컴포넌트와 Props`, `Hooks`를 사용하여 간단한 `투두 리스트`를 같이 만들어보는 시간을 가졌습니다. @@ -118,15 +117,15 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/9523c80b-6670-4 4~5일차 기간에는 지금까지 배운 `React` 지식을 활용해서 `REST API` 서버와 통신하여 **게시판을 만들어보는 시간**을 가졌습니다. -![Styled Components](https://images.velog.io/images/jongsunpark88/post/a95507d6-6278-40c4-aed5-8f0ac23edeb5/style300.png) +![Styled Components](/images/posts/life/gbsw-frontend-lecture/styled-components.png) 게시판을 만들기전에 알면 좋은 지식들을 일부 알려줬는데요. 첫번째는 `CSS in JS` 방식을 통해서 컴포넌트를 스타일링 할 수 있는 `styled-components` 라이브러리를 사용하는 방법을 알려줬습니다. 학생들이 `CSS` 파일을 사용하지 않고도 스타일링 할 수 있다는것을 보고 신기하다는 반응이 있었습니다. -![REST API](https://github.com/yiyb0603/yiyb-blog/assets/50941453/41be0386-6b09-4c98-8b71-d6db05ab9120) +![REST API](/images/posts/life/gbsw-frontend-lecture/rest-api.png) 두번째는 서버와의 통신시 필수적으로 알아야하는 `REST API`의 개념에 대해서 설명했습니다. **HTTP 메소드별 활용방법** 및 **자원을 나타내는 URL의 구조** 등을 알려줬습니다. -![브라우저 저장소](https://github.com/yiyb0603/yiyb-blog/assets/50941453/e87d1dbf-54e8-4a65-9f7a-f09b2bde7a38) +![브라우저 저장소](/images/posts/life/gbsw-frontend-lecture/browser-storages.png) 마지막으로는 브라우저에서 데이터를 저장하는 여러가지 방법`(Cookie, Browser Storage)`과 각 저장소별 차이점에 대해서 알아보는 시간을 가졌습니다. diff --git a/posts/nextjs-contentlayer-blog.mdx b/posts/Next.js/nextjs-contentlayer-blog.mdx similarity index 93% rename from posts/nextjs-contentlayer-blog.mdx rename to posts/Next.js/nextjs-contentlayer-blog.mdx index 9d811cc..4707e13 100644 --- a/posts/nextjs-contentlayer-blog.mdx +++ b/posts/Next.js/nextjs-contentlayer-blog.mdx @@ -1,9 +1,8 @@ --- title: Next.js에서 contentlayer 사용하여 손쉽게 정적블로그 만들기 description: contentlayer 라이브러리를 사용하여 손쉽게 정적블로그를 만드는 과정을 공유합니다. -category: Next.js createdAt: 2023-02-26 -thumbnail: https://user-images.githubusercontent.com/50941453/221415312-71c7d9cc-9a07-4356-aa54-5f14cdc9ddc6.png +thumbnail: /images/posts/next.js/nextjs-contentlayer-blog/thumbnail.png --- 안녕하세요! 정적 블로그를 만들고나서, 처음으로 글을 작성해보네요. @@ -13,13 +12,17 @@ thumbnail: https://user-images.githubusercontent.com/50941453/221415312-71c7d9cc ## 1. 왜 만들게되었는가? 🤔 저는 기존에 [Velog](https://velog.io/@yiyb0603) 플랫폼을 이용해서 개발에 관한 글들을 적어오곤 했는데요, 벨로그도 저한테는 굉장히 좋은 개발자 블로그 서비스였으나, 최근들어서 **나만의 Playground**를 목적으로 간단하게 정적블로그를 만들어보고 싶다. 라는 생각이 자주 들었습니다. +
+ 그러다가 이틀전인 금요일, 회사에서 [Vercel Templates](https://vercel.com/templates) 페이지를 둘러보다가 `혹시 정적 블로그를 만들기 쉬운 템플릿 or 라이브러리가 있지 않을까?` 하는 생각에 이것저것 찾아보게 되었습니다.
+ 그리고 [contentlayer](https://www.contentlayer.dev/) 라는 Next.js에서 정적블로그를 손쉽게 제작할 수 있도록 도와주는 아주 Awesome한 라이브러리를 찾게되어, 당일에 퇴근하고나서부터 주말까지 쭉 개발만하게 되었네요.
+ 단순 개발 블로그가 아닌 **일상, 잡담, 취미** 등의 글들도 저 마음대로 작성할수 있게끔 하고 싶었습니다. > 아니 님아 그러면 그냥 네이버 블로그에다가 글 작성하면 되는거 아님?? @@ -29,18 +32,25 @@ thumbnail: https://user-images.githubusercontent.com/50941453/221415312-71c7d9cc 일반적인 네이버, 티스토리 블로그 플랫폼의 가장 큰 단점은 커스텀 부분에서 한계가 있다라고 생각했기때문에 직접 만들어보고 싶었습니다.
+ 추가적으로 나중에는 포트폴리오 페이지를 블로그에 추가시켜서 저를 더 표현하는 웹사이트를 만들계획입니다.
+ 예전에 [Gatsby.js](https://www.gatsbyjs.com/)도 눈여겨보았으나, Next.js를 사용하고싶다라는 마음이 컸기에 패스했습니다. + 만약 React.js만으로 블로그를 제작하시려는 분들에게는 아주 좋은 도구에요.
+ (contentlayer에 대해서는 밑 섹션에 추가로 적어두었습니다.) +
댓글 기능의 경우에는 [GitHub Utterances](https://utteranc.es/)가 정말 좋다고 생각되어 추가하였습니다. + 웹 배포는 회사에서 사용하면서 정말 간편하다고 생각되는 [Vercel](https://vercel.com) 플랫폼을 사용하여 배포하였습니다. +
라이트모드 / 다크모드 테마 설정의 경우에는 기본적으로 시스템 테마를 따르며, 이후에는 사용자가 선택한 테마로 지정되도록 구현했습니다. @@ -108,10 +118,7 @@ tsconfig.json에서는 import 경로의 alias를 설정해줍니다. ```typescript // contentlayer.config.ts -import { - defineDocumentType, - makeSource, -} from 'contentlayer/source-files'; +import { defineDocumentType, makeSource } from 'contentlayer/source-files'; import highlight from 'rehype-highlight'; import rehypePrettyCode from 'rehype-pretty-code'; @@ -173,6 +180,7 @@ export default contentSource; ``` `contentlayer.config.ts`에서 작성한 내용중에서 **fields** 객체에 작성된 데이터 필드가 핵심인데요, + 항상 마크다운 글 최상단에 글에는 글에 대한 정보를 `---` 블록을 통해서 필드로 적어주어야 합니다. 이제 프로젝트 Root 경로에 `posts` 폴더를 만든다음, **hello.mdx** 파일을 하나 만들어보겠습니다. @@ -211,7 +219,7 @@ export default PostDetailPage; 일단 여기까지만 하고나서, `npm run dev` 명령어를 입력해서 잠깐 개발서버를 실행하면 어떤일이 일어나는지 지켜봅시다. -![contentlayer](https://user-images.githubusercontent.com/50941453/221409388-251cd493-628f-4b5e-9c69-e1189b699076.png) +![contentlayer](/images/posts/next.js/nextjs-contentlayer-blog/contentlayer-generated.png) 프로젝트 Root 경로에 `.contentlayer` 명의 폴더가 생기고, 하위 폴더와 파일들이 생겨난것을 볼 수 있습니다! @@ -234,6 +242,7 @@ export declare const allDocuments: DocumentTypes[]; > Post: **contentlayer.config.ts**에서 등록한 필드들을 타입스크립트의 타입으로 쓸수 있는 타입입니다. 즉, 마크다운 글 데이터에 접근하려면 Post[] 타입을 띄고있는 allPosts 배열에 접근하면 손쉽게 글 데이터를 얻어올수 있다는 뜻입니다. 너무쉽죠? +
이점을 바탕으로 `pages/posts/[id].tsx` 파일을 마저 작성해보겠습니다. @@ -242,7 +251,7 @@ export declare const allDocuments: DocumentTypes[]; 아, 그리고 정적블로그의 특성상 Next.js의 SSG(Static Site Generation) 렌더링 방식에 최적화 되어있기에, `getStaticProps`와 `getStaticPaths` 함수를 사용하여 SSG 렌더링도 같이 구현해봅시다. -> Next.js의 SSG 렌더링: **빌드 타임**에 getStaticPaths로 지정된 경로들의 html 파일을 생성하는 방식, 미리 생성된 html 파일을 다운로드하는 방식이기에 속도가 매우 빨라진다. +> Next.js의 SSG 렌더링: **빌드 타임**에 getStaticPaths로 지정된 경로들의 html 파일을 생성하는 방식, 미리 생성된 html 파일을 다운로드하는 방식이기에 속도가 매우 빨라진다. ```typescript import { @@ -262,7 +271,7 @@ const PostDetailPage = ({

{post?.title}

// 안녕하세요. 제목이에요. {post?.category} // 카테고리1 - + /* 마크다운으로 작성된 콘텐츠 렌더링 (안녕하세요. 오늘은 React.js 프로젝트를 만들어볼게요.) @@ -294,7 +303,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => { const postId = String(params?.id || ''); // 동적 id 파라미터를 통해서 파일명과 동일한 글을 찾아서 리턴합니다. - const post = allPosts.find(({ _raw }) => { + const post = allPosts.find(({ url }) => { return _raw.flattenedPath === postId; }); @@ -308,24 +317,26 @@ export const getStaticProps: GetStaticProps = async ({ params }) => { `allPosts` 배열을 불러온다음, 동적으로 받는 id 파라미터를 통해서 렌더링할 마크다운 파일을 찾는 로직을 위와같이 구현할 수 있습니다. 그런 다음 `useMDXComponent` hooks를 통해서 손쉽게 마크다운 코드를 html 형식으로 렌더링을 할 수 있습니다. -![](https://user-images.githubusercontent.com/50941453/221416002-6dc56441-f4c9-481a-832e-b7381256e786.png) +![구현 성공](/images/posts/next.js/nextjs-contentlayer-blog/thumb.png) 지금까지 **글 상세 페이지** 구현을 예제로 코드를 작성해봤는데, 만약 글 목록 페이지를 구현해도 정말 손쉽게 할 수 있겠죠? 😀 그래서 이 과정은 이글에서 생략하겠습니다! (참고링크에서 도움이 됩니다.) ### 2-2. 참고링크 🙇‍♂️ + [https://miryang.dev/blog/build-blog-with-nextjs](https://miryang.dev/blog/build-blog-with-nextjs) [https://maintainhoon.vercel.app/blog/post/blog_development_period](https://maintainhoon.vercel.app/blog/post/blog_development_period) ## 3. 개발중에 발생한 SWC Issue 😥 + 이번 블로그 개발중에 상당히 골치아픈 버그를 하나 만났었는데요, 개발중간에 production 모드로 실행을 해보았는데, **client side exception**이 발생하면서 아래의 오류가 출력되었습니다. -![](https://user-images.githubusercontent.com/50941453/221411200-c4024984-08e9-40dc-84bb-26dfa21c7b0d.png) +![한글 유니코드 오류](/images/posts/next.js/nextjs-contentlayer-blog/unicode-error.png) 해당 오류는 **마크다운 내용에 한글이 들어간 경우**에만 발생했고, 한글 유니코드 관련된 오류로 보였습니다. 저는 한글이 없으면 생존이 불가능한 사람이기에 무조건 오류를 해결해야 했습니다. -![](https://user-images.githubusercontent.com/50941453/221416235-29a82712-830f-4e28-bbcb-0c1918c1b090.png) +![절망의 오류](/images/posts/next.js/nextjs-contentlayer-blog/despair.png) 하지만 위에 제가 첨부한 **참고링크** 블로그 글, 공식문서, 다른분들이 작성한 contentlayer 프로젝트 코드 등등 닥치는데로 찾아봤지만 이 오류를 해결하는 방법을 전혀 찾을 수 없었습니다. 😥😥 @@ -333,17 +344,21 @@ export const getStaticProps: GetStaticProps = async ({ params }) => { 결국 그렇게 잠이들었고.. 다음날 기상한 저는 **혹시나하고** `next.config.js`의 `swcMinify` 속성을 **false**로 변경하고, production으로 실행해보니 이게 왠일!! **오류가 더이상 나타나지 않았습니다.** -![](https://user-images.githubusercontent.com/50941453/221416461-7b5d3f05-4b9e-42c5-b713-28ca1018d926.png) +![너무 많은 오류들](/images/posts/next.js/nextjs-contentlayer-blog/so-many-errors.png)
+ 제가 인터넷에서 찾은 예제들에서 이 오류가 나타나지 않았던 것은, 많은분들이 Next 12 버전으로 작성하셨는데 swcMinify 속성은 Next 13부터 기본값이 true로 변경되었습니다. + 저는 Next.js 13을 사용하고있었기 때문에 기본적으로 swcMinify가 활성화 되어있던 것이죠.
+ 즉, 대부분이 swcMinify를 활성화하지 않은상태로 개발하셨기 때문에 이 오류에 대해서 잘 나오지 않았던 것이었습니다. ## 4. 앞으로의 계획 -![](https://user-images.githubusercontent.com/50941453/221416641-bd9611e7-6766-47a2-8bd4-a75ed96f757c.png) + +![넌 계획이 다 있구나](/images/posts/next.js/nextjs-contentlayer-blog/you-have-a-plan.png) 자주는 아니지만 간간히 블로그 글을 작성하면서 블로그에 새로운 기능이나 `third-party`를 하나씩 추가해나가려고 합니다. @@ -358,4 +373,4 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
이정도까지만 현재 계획중인데, 나중에 더 생길수도 있을듯 합니다. 😶 -이만 저는 물러가보도록 하겠습니다! 글 읽어주셔서 감사드리며, 다음에도 많이 찾아와주세요! 😀 \ No newline at end of file +이만 저는 물러가보도록 하겠습니다! 글 읽어주셔서 감사드리며, 다음에도 많이 찾아와주세요! 😀 diff --git a/posts/nextjs-dynamic-sitemap.mdx b/posts/Next.js/nextjs-dynamic-sitemap.mdx similarity index 88% rename from posts/nextjs-dynamic-sitemap.mdx rename to posts/Next.js/nextjs-dynamic-sitemap.mdx index 86681e3..5079570 100644 --- a/posts/nextjs-dynamic-sitemap.mdx +++ b/posts/Next.js/nextjs-dynamic-sitemap.mdx @@ -2,25 +2,22 @@ title: Next.js에서 동적 사이트맵 제작하기 description: 안녕하세요! 이번에는 Next.js에서 동적 URL 형식의 사이트맵 만들기를 주제로 글을 작성해보겠습니다. createdAt: 2023-02-27 -category: Next.js -thumbnail: https://velog.velcdn.com/images/yiyb0603/post/812c2dcb-4f2f-451c-a0f9-a36a8b99ae50/image.png +thumbnail: /images/posts/next.js/nextjs-dynamic-sitemap/thumbnail.png --- -안녕하세요! 정말 오랜만에 Velog 글을 적어보려고 합니다. 그동안 `아 이거는 나중에 Velog 글 주제로 적어 봐야겠다!` 라고 생각을 해놓고는 계속 미뤄왔었네요. 🥲🥲 그래서 앞으로 글을 적을때 회사 프로젝트를 진행하면서 알게되었던것들을 순서대로 적어보려고 합니다. - -이번 시간에는 이전에 회사 프로젝트를 진행하면서 가장 핵심적인 액션중 하나인 **동적 사이트맵 제작**을 주제로 글을 적어보려고 합니다. +안녕하세요! 이번 시간에는 이전에 회사 프로젝트를 진행하면서 가장 핵심적인 액션중 하나인 **동적 사이트맵 제작**을 주제로 글을 적어보려고 합니다. ## 1. 사이트맵이 뭐야? 🧐 ### 1-1. 검색엔진 최적화에서의 중요성 -![](https://velog.velcdn.com/images/yiyb0603/post/b5e8623a-2edc-4a39-a228-6c5e9b0dc445/image.png) +![검색엔진 크롤링](/images/posts/next.js/nextjs-dynamic-sitemap/crawling.png) 현대에는 다양한 검색엔진 사이트가 있습니다. 우리나라에서는 대표적으로 **구글, 네이버, 다음**을 뽑을 수 있습니다. 이 검색엔진에서는 다양한 콘텐츠들을 노출시키기 위해서 **크롤러 봇**이 각종 사이트들을 돌아다니면서 링크를 수집하게 됩니다. -![](https://velog.velcdn.com/images/yiyb0603/post/25a3b9b5-1a3e-4d96-88a3-362809340066/image.png) +![Velog 플랫폼](/images/posts/next.js/nextjs-dynamic-sitemap/velog.png) -현재 저희가 사용하고있는 Velog를 예시로 들어서 크롤러 봇의 행동과정을 설명해보겠습니다. +대표적인 기술 플랫폼인 `Velog`를 예시로 들어서 크롤러 봇의 행동과정을 설명해보겠습니다. > 1. 크롤러 봇이 https://velog.io 사이트로 진입 > 2. 크롤러 봇이 사이트를 보면서 **수집가능한 페이지**인지 확인하기 위해서 각 글에 달린 링크들에 타고 들어가게 됩니다. @@ -33,13 +30,13 @@ thumbnail: https://velog.velcdn.com/images/yiyb0603/post/812c2dcb-4f2f-451c-a0f9 이러한 문제를 해결하기 위해서 **크롤러 봇에게 사이트 크롤링 가이드 라인 맵을 제공**해주는 `사이트맵`이 등장하게 되었습니다. -![](https://velog.velcdn.com/images/yiyb0603/post/e54351e2-282e-40d8-bbc5-bb0604bdf61e/image.png) +![사이트맵](/images/posts/next.js/nextjs-dynamic-sitemap/sitemap.png) 사이트맵은 검색엔진에 노출되어야하는 페이지의 링크들을 모아놓았으며, 크롤러 봇의 페이지 수집을 위한 지도 역할을 해줍니다. Velog 서비스로 예시를 들면, 검색엔진에 노출되기 위해서 수집을 원하는 글들의 링크들을 모아놓은 사이트맵을 만들어 놓는다면 크롤러 봇은 **이전에 연결된 링크들을 통한 페이지 수집** 과정에서 사이트 내 노출이 안되었던 누락된 글들을 사이트맵을 통해서 수집 가능하도록 할 수 있게 됩니다. -> **이외의 사이트맵에 대한 정보를 알고싶다면 아래의 구글 사이트맵 가이드라인을 통해서 더 알아볼 수 있습니다.** [https://developers.google.com/search/docs/advanced/sitemaps/overview?hl=ko](https://developers.google.com/search/docs/advanced/sitemaps/overview?hl=ko) +> **이외의 사이트맵에 대한 정보를 알고싶다면 아래의 구글 사이트맵 가이드라인을 통해서 더 알아볼 수 있습니다.** > [https://developers.google.com/search/docs/advanced/sitemaps/overview?hl=ko](https://developers.google.com/search/docs/advanced/sitemaps/overview?hl=ko) ## 2. Next.js를 사용한 동적 사이트맵 구현 @@ -51,14 +48,14 @@ Velog 서비스로 예시를 들면, 검색엔진에 노출되기 위해서 수 > **npx create-next-app --typescript** 를 실행후 터미널에서 묻는 질문들을 스텝별로 입력해주면 프로젝트가 생성됩니다. -![](https://velog.velcdn.com/images/yiyb0603/post/023d03c7-8dfd-406c-abcd-32ada8e8c75d/image.png) +![프로젝트 생성](/images/posts/next.js/nextjs-dynamic-sitemap/create-project.png) 프로젝트가 성공적으로 생성되고 나서 필요한 패키지들을 설치해줍시다! 저의 경우에는 아래의 패키지들을 설치해주었습니다. > **yarn add axios @emotion/react dayjs** -![](https://velog.velcdn.com/images/yiyb0603/post/6d03971d-7c3f-4c82-9f4a-e7b12ffbe6e2/image.png) +![사이트맵 파일 생성](/images/posts/next.js/nextjs-dynamic-sitemap/create-sitemap-file.png) 사이트맵은 `xml` 파일 형식으로 제작되어야 합니다. 그래서 페이지 URL 상으로는 `~~/*.xml` 형식으로 나타나야 하는데요, pages 폴더를 통한 라우팅을 지원해주는 Next.js에서는 pages 폴더에 **sitemap.xml.ts** 라는 이름으로 파일을 만들어 주시면 됩니다 @@ -166,7 +163,7 @@ Sitemap.getInitialProps = async (ctx) => { 위의 코드에서 `console.log(posts)`를 찍어보면 아래와 같은 글 목록 정보들이 나옵니다. -![](https://velog.velcdn.com/images/yiyb0603/post/f26ef5f4-9d6f-4f36-86f8-5d02966ee0d3/image.png) +![jsonplaceholder 응답](/images/posts/next.js/nextjs-dynamic-sitemap/jsonplaceholder-response.png) 불러온 글들이 약 100개 정도로 보이는데요, 이제 이 100개의 글 정보들을 xml로 담아줍시다 😁 @@ -260,14 +257,14 @@ export default PostPage; ``` > http://localhost:3000/posts/2 -> ![](https://velog.velcdn.com/images/yiyb0603/post/8027d8c5-f874-4879-a91b-db0c9c14ad75/image.png) +> ![2번째 글](/images/posts/next.js/nextjs-dynamic-sitemap/second-post.png) > http://localhost:3000/posts/10 -> ![](https://velog.velcdn.com/images/yiyb0603/post/d81ee15b-2ab0-45af-9400-08230207dcf9/image.png) +> ![10번째 글](/images/posts/next.js/nextjs-dynamic-sitemap/ten-post.png) 이제 프로젝트를 실행하여 `localhost:3000/sitemap.xml` 경로로 들어가볼까요? 🐳 -![](https://velog.velcdn.com/images/yiyb0603/post/f976c1b2-f443-4024-9558-9f07bea4f8ae/image.png) +![만들어진 사이트맵](/images/posts/next.js/nextjs-dynamic-sitemap/created-sitemap.png) API로 불러왔던 글들의 정보들이 모두 사이트맵에 등록되었습니다. 짝짝짝 🎶 diff --git a/posts/nextjs-standalone.mdx b/posts/Next.js/nextjs-standalone.mdx similarity index 90% rename from posts/nextjs-standalone.mdx rename to posts/Next.js/nextjs-standalone.mdx index 7e01ff7..2a61c89 100644 --- a/posts/nextjs-standalone.mdx +++ b/posts/Next.js/nextjs-standalone.mdx @@ -1,9 +1,8 @@ --- title: Next.js의 standalone 빌드 알아보기 description: 독립적으로 실행 가능하고, 빌드 용량을 최적화 해주는 standalone 빌드를 알아봅시다. -category: Next.js createdAt: 2023-12-08 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/f94d1be6-d69e-42f6-b3a8-e1d2678121eb +thumbnail: /images/posts/next.js/nextjs-standalone/thumbnail.png --- 안녕하세요! 오늘은 Next.js의 빌드 결과물을 표현하는 방식 중 하나인 `standalone` 옵션에 대해서 알아보겠습니다. `standalone` 옵션은 자체구축 혹은 도커 환경에서 더욱 빛을 발휘하는데요. 어떤 특징이 있는지 알아보겠습니다. @@ -41,7 +40,7 @@ const options = { `standalone`을 사용한 빌드 결과물은 어떻게 나올까요? `next build`를 사용하여 빌드한 결과, `.next/standalone` 폴더에 결과물이 빌드되었습니다. 기존 빌드 결과물과 비교했을때 `node_modules` 파일이 정말 많이 줄었고, `.next/standalone` 폴더안에 있는 내용물만 서버로 가져가면 되기 때문에 코드 파일 수도 많이 줄었네요. -![standalone 빌드 결과물](https://github.com/yiyb0603/yiyb-blog/assets/50941453/60ba4d7d-d85d-495e-8f14-ad807697541b) +![standalone 빌드 결과물](/images/posts/next.js/nextjs-standalone/standalone-build-result.png) 이중에서 `.next/standalone/server.js` 파일이 있는데요. 이 파일은 서버의 포트번호 등을 설정하여 실행 가능하도록 지정하는 파일입니다. `node` 명령어를 사용하여 실행시키면 Next.js 프로젝트가 Production 모드로 실행됩니다. @@ -63,7 +62,7 @@ node .next/standalone/server.js 맨 처음에 `server.js`를 실행하여 프로젝트를 구동했을때 여러분이 생각한 모습과는 다르게 웹이 비정상적으로 동작하는데요. 네트워크 탭을 열어보시면 대부분의 **Chunk 파일**, **public 경로에 저장한 이미지, 폰트 파일** 등이 존재하지 않아서 오류가 발생함을 알 수 있습니다. -![public, chunk 찾을 수 없음](https://github.com/yiyb0603/yiyb-blog/assets/50941453/bb1ac7ba-4a59-45b6-a522-1ebac3e0f440) +![public, chunk 찾을 수 없음](/images/posts/next.js/nextjs-standalone/static-resource-failed.png)
@@ -80,7 +79,7 @@ cp -r .next/static .next/standalone/.next 이후 재실행하면 파일들을 성공적으로 불러왔음을 알 수 있습니다. -![public, static 불러오기 성공](https://github.com/yiyb0603/yiyb-blog/assets/50941453/6780a981-f6cb-40a7-ae5b-9f43428846c0) +![public, static 불러오기 성공](/images/posts/next.js/nextjs-standalone/static-resource-success.png) ## 3. 마치며 📌 diff --git a/posts/error-boundary-with-react-query.mdx b/posts/React-Query/error-boundary-with-react-query.mdx similarity index 94% rename from posts/error-boundary-with-react-query.mdx rename to posts/React-Query/error-boundary-with-react-query.mdx index e3770b3..50fb173 100644 --- a/posts/error-boundary-with-react-query.mdx +++ b/posts/React-Query/error-boundary-with-react-query.mdx @@ -1,16 +1,18 @@ --- title: Error Boundary, React-Query와 함께 사용해보기 description: 리액트의 Error Boundary를 React-Query 라이브러리와 함께 사용해봅시다. -category: React-Query createdAt: 2023-06-28 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/154a16c2-f5a6-489f-9639-85685bfe794a +thumbnail: /images/posts/react-query/error-boundary-with-react-query/thumbnail.png --- 안녕하세요! 오늘은 리액트의 주요 기능중 하나인 `Error Boundary`를 `React-Query` 상태 관리 라이브러리와 함께 사용해보는 방법을 알아보겠습니다. 😃 ## 1. Error Boundary란? 🤔 + `Error Boundary (에러 바운더리)`는 리액트 16버전에서 추가된 기능으로, **하위 컴포넌트에서 발생한 에러를 잡아주는** 컴포넌트 역할을 수행합니다. +
+ 물론, `Error Boundary`를 사용하지 않아도 아래 코드처럼 기본적인 오류처리는 가능합니다. ```tsx @@ -33,13 +35,19 @@ const MyComponent = () => { ``` 위와 같은 형식의 코드는 **명령형** 코드로 분류가 되는데요. 물론 명령형 코드가 나쁘진 않지만, 명령에 대한 코드를 작성하게되면 코드 라인이 점점 증가할 수 있습니다. +
+ 그리고 위와같은 명령형 코드를 `Error Boundary`를 사용하면 간편하게, 그리고 **선언적**인 오류처리 코드를 작성할 수 있습니다. 어떻게 처리할 수 있는지는 아래 섹션을 통해서 알아봅시다! ## 2. 컴포넌트 제작 ⚒️ + 참고로 `Error Boundary` 컴포넌트는 리액트 패키지에서 그저 띡! import! 만 해서 사용할 수 있는것이 아니고, 리액트의 **클래스형 컴포넌트의 메소드**를 오버라이딩 해서 만들 수 있습니다. + 따라서, 현재는 **함수형 컴포넌트**를 사용해서 `Error Boundary`를 구현 하는것은 불가능합니다. 😥 +
+ 먼저, **ErrorBoundary.tsx** 라는 컴포넌트 파일을 만들고, 아래의 코드를 작성해주세요. 각 코드에 대한 설명은 주석을 참고하시면 됩니다. 😛 ```tsx @@ -48,11 +56,11 @@ import { Component, ReactNode, ErrorInfo } from 'react'; type ErrorBoundaryState = { hasError: boolean; error: Error | null; -} +}; type ErrorBoundaryProps = { children: ReactNode; -} +}; class ErrorBoundary extends Component { constructor(props: ErrorBoundaryProps) { @@ -80,7 +88,7 @@ class ErrorBoundary extends Component { 주로 오류를 로깅해야 할때 해당 메소드에 접근해서 로깅할 수 있습니다. */ componentDidCatch(error: Error, errorInfo: ErrorInfo): void { - console.log({ error, errorInfo }) + console.log({ error, errorInfo }); } render() { @@ -103,7 +111,9 @@ class ErrorBoundary extends Component { 3. 만약 API 오류가 났을때 재요청 처리를 해야하는데, 어떻게 구현해야 하는가? 위 두가지 문제중 가장 먼저 1번과 2번 문제를 해결해보도록 하겠습니다. `fallback` props에는 **인자를 넘겨주는 컴포넌트** 형태로 props를 받도록 지정하면 됩니다. +
+ `Error Boundary`를 사용하는 컴포넌트 쪽에서 오류의 정보를 전달하는 목적으로요. ```tsx @@ -118,17 +128,17 @@ import { type ErrorBoundaryState = { hasError: boolean; error: Error | null; -} +}; type FallbackProps = { error: Error | null; -} +}; type ErrorBoundaryProps = { // fallback 용도의 컴포넌트는 Error 정보를 props로 받을 수 있는 컴포넌트여야 한다. fallback: ComponentType; children: ReactNode; -} +}; class ErrorBoundary extends Component { constructor(props: ErrorBoundaryProps) { @@ -148,7 +158,7 @@ class ErrorBoundary extends Component { } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { - console.log({ error, errorInfo }) + console.log({ error, errorInfo }); } render() { @@ -201,10 +211,13 @@ const ParentComponent = () => { ``` `Error Boundary` 컴포넌트를 설계할때, 오류에 대한 정보를 `매개변수`로 받을 수 있기때문에 오류 케이스별로 조건부 렌더링이 가능해집니다. 정말 편리하죠? +
+ 하지만 아직 해결한 단점이 하나 남아있는데요, **만약 API 오류가 났을때 재요청 처리를 해야하는데, 어떻게 구현해야 하는가?** 항목입니다. 이 항목의 해결법은 밑 섹션에서 추가로 함께 다루겠습니다. ## 3. React-Query와 함께 사용 🚀 + React-Query는 `Error Boundary` 사용을 지원하는데요, 사용방법은 정말 간단합니다. `useQuery` 훅의 `useErrorBoundary` 옵션을 `true`로 지정해주시면 됩니다. 아래처럼 말이죠. ```typescript @@ -222,7 +235,7 @@ const { data } = useQuery({ ```tsx const fetchData = async () => { // return something -} +}; const ChildComponent = () => { const { data } = useQuery({ @@ -237,66 +250,71 @@ const ChildComponent = () => {

글 내용: {data.content}

); -} +}; const ParentComponent = () => { return ( { + fallback={({ error }) => { const status = error?.status; switch (status) { case 404: - return ( -

404 오류가 발생했습니다!

- ); + return

404 오류가 발생했습니다!

; case 500: - return ( -

서버 오류에요!

- ); + return

서버 오류에요!

; } - })} + }} >
); -} +}; ``` `React Query`와 함께 사용하는 경우에는, `useQuery` hook을 호출한 **컴포넌트의 상위에서 감싸야한다**는 규칙을 꼭 알아두셔야 합니다. ### 3-1. 재요청 처리하기 + 일반적으로 서비스에서 갑작스럽게 오류가 발생한경우 **다시 시도하기** 버튼을 통해서 재요청 동작이 필요한 서비스에서는 `Error Boundary`를 어떻게 사용해야 할까요? +
+ 참고로 `React Query` 에서는 가장 가까운곳에서 `Error Boundary`를 사용하는 `useQuery` hook의 오류 상태를 초기화 시켜주는 `QueryErrorResetBoundary` 컴포넌트가 있습니다. > Query Error Reset Boundary 컴포넌트에 대해 알고싶다면? -https://tanstack.com/query/v4/docs/react/reference/QueryErrorResetBoundary +> https://tanstack.com/query/v4/docs/react/reference/QueryErrorResetBoundary `QueryErrorResetBoundary`는 `reset` 함수를 매개변수로 받아서 JSX를 렌더링하도록 설계된 컴포넌트인데요, `reset` 함수는 아까전에 말씀드린 `useQuery` hook의 오류 상태를 초기화하는 함수입니다. +
+ 이제 위의 코드를 조금 수정해보겠습니다. ```tsx return ( - {(({ reset }) => ( + {({ reset }) => ( - ))} + )} ); ``` 코드를 보시면 `Error Boundary`에 `onReset` 이라는 이름으로 `reset` 함수를 props로 넘겨주려고 하는 이유는, `Error Boundary` 컴포넌트 내에 정의된 `hasError` 상태를 `false`로 리셋하기 위함 입니다. +
+ 만약 사용자가 **재시도 요청**을 보냈을때는 오류 UI가 아닌 로딩 UI를 보여주거나 해야겠죠? 따라서 UI가 변경되어야함을 `Error Boundary` 컴포넌트에게 알리기 위해서 `reset` 함수를 넘겨줍니다. +
+ `Error Boundary` 컴포넌트에 `onReset` props를 넘겨주기 위해서 `ErrorBoundary.tsx` 파일을 아래처럼 수정해줍니다. ```tsx @@ -311,18 +329,18 @@ import { type ErrorBoundaryState = { hasError: boolean; error: Error | null; -} +}; type FallbackProps = { error: Error | null; resetErrorBoundary: () => void; // resetErrorBoundary 함수를 사용하는쪽에서 사용 가능하도록 props로 전달 -} +}; type ErrorBoundaryProps = { fallback: ComponentType; onReset: () => void; children: ReactNode; -} +}; class ErrorBoundary extends Component { constructor(props: ErrorBoundaryProps) { @@ -355,7 +373,7 @@ class ErrorBoundary extends Component { } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { - console.log({ error, errorInfo }) + console.log({ error, errorInfo }); } render() { @@ -382,17 +400,15 @@ class ErrorBoundary extends Component { ```tsx return ( - {(({ reset }) => ( + {({ reset }) => ( { + fallback={({ error, resetErrorBoundary }) => { const status = error?.status; switch (status) { case 404: - return ( -

404 오류가 발생했습니다!

- ); + return

404 오류가 발생했습니다!

; case 500: return ( @@ -403,20 +419,23 @@ return ( ); } - })} + }} >
- ))} + )}
); ``` 위에서 `resetErrorBoundary` 함수 또한 이전에 `error` 변수처럼 props용 함수에서 매개변수로 받을 수 있도록 처리해주었는데요. 이는 `Error Boundary`의 `hasError` 상태 초기화 동작, `reset` 함수의 동작이 합쳐진 함수를 `Error Boundary`를 사용하는 쪽에서 사용할 수 있도록 제공하기위함 입니다. +
+ 보통 오류 UI안에 `재시도 하기` 버튼이 위치할 수 있는 상황이 많기 때문에 이를 대비할 수 있죠. ### 3-2. 선택적 useErrorBoundary + 만약 `Error Boundary`를 사용하시다가 이런상황에 마주하실 수도 있습니다. > 저는 400과 500 Status 에러일때는 Error Boundary를 사용하고 싶은데, 나머지 Status 에러일때는 사용하고 싶지 않아요. @@ -436,13 +455,18 @@ useQuery({ useErrorBoundary: (error) => { // 오류 Status가 400이거나 500일때만 Error Boundary 사용하도록. return error.status === 400 || error.status === 500; - } -}) + }, +}); ``` ## 4. 마치며 📌 + 오늘은 리액트의 `Error Boundary`란 무엇이며, 이를 `React Query`와 함께 활용해보면서 각 케이스별로 코드를 작성하는 방법에 대해 알아보았습니다. +
-지금 당장은 이해하기 어려우실수도 있지만, 직접 도전해보면서 여러번 읽으시면 도움이 됩니다. 😛 + +지금 당장은 이해하기 어려우실수도 있지만, 직접 도전해보면서 여러번 읽으시면 도움이 됩니다. 😀 +
-이상으로 글을 마무리 하겠습니다. 긴 글 읽어주셔서 감사합니다! 🧑‍🏫 \ No newline at end of file + +이상으로 글을 마무리 하겠습니다. 긴 글 읽어주셔서 감사합니다! 🧑‍🏫 diff --git a/posts/react-query-loading-state.mdx b/posts/React-Query/react-query-loading-state.mdx similarity index 76% rename from posts/react-query-loading-state.mdx rename to posts/React-Query/react-query-loading-state.mdx index 055ba88..5395357 100644 --- a/posts/react-query-loading-state.mdx +++ b/posts/React-Query/react-query-loading-state.mdx @@ -2,12 +2,13 @@ title: React-Query의 isLoading, isFetching 그리고 isInitialLoading description: 안녕하세요! 오늘은 React-Query의 상태를 표현하는 isLoading, isFetching 그리고 isInitialLoading에 대해서 알아보겠습니다. createdAt: 2023-03-30 -category: React-Query -thumbnail: https://user-images.githubusercontent.com/50941453/228815735-4dd016c3-eab4-4fc9-ab90-38b2ade05dcc.png +thumbnail: /images/posts/react-query/react-query-loading-state/thumbnail.png --- + 안녕하세요! 오늘은 React-Query의 상태를 표현하는 isLoading, isFetching 그리고 isInitialLoading에 대해서 알아보겠습니다. 😃 세개의 속성모두 React-Query에서 API 호출을 했을때 상태가 변경된다는 공통점이 있는데요, 하지만 특정 상황에 따라서 세개의 속성은 각기 다르게 동작합니다. 그 이유를 함께 알아봅시다. ## 1. staleTime과 cacheTime ⏰ + 본격적으로 알아보기전, React-Query의 `staleTime`과 `cacheTime`의 개념을 먼저 알고가야합니다. React-Query에서 제공하는 `useQuery` hooks의 사용법은 아래와 같습니다. ```typescript @@ -15,7 +16,7 @@ import { useQuery } from '@tanstack/react-query'; const fetchData = async () => { return 'some data'; -} +}; const {} = useQuery({ queryKey: ['some', 'query'], @@ -26,31 +27,45 @@ const {} = useQuery({ ``` useQuery에 지정하는 옵션들중 `staleTime`과 `cacheTime`에 주목을 해야하는데요, 이 두 속성의 의미는 다음과 같습니다. + > **staleTime**: 동일한 queryKey에 대해서 중복요청을 제거할 밀리세컨드 시간, 캐시에 저장된 데이터의 신선도를 의미 -**cacheTime**: queryKey를 통해서 불러온 데이터를 캐시에 언제까지 저장할것인지를 지정하는 밀리세컨드 시간, 이 시간이 지나면 가비지 컬렉터(GC)가 캐시안의 데이터를 수집하게된다. +> **cacheTime**: queryKey를 통해서 불러온 데이터를 캐시에 언제까지 저장할것인지를 지정하는 밀리세컨드 시간, 이 시간이 지나면 가비지 컬렉터(GC)가 캐시안의 데이터를 수집하게된다. 위의 예제에서 staleTime을 `60 * 1000`으로 지정했는데요, 이는 1분동안 동일한 요청에 대해서 API 요청을 날리지 않게 됩니다. (캐시에 저장되어있는 데이터 사용) +
-cacheTime의 경우에는 `Infinity`로 지정했는데요, 이는 영구적으로 캐시에 저장하고 있겠다라는 의미입니다. 만약 cacheTime을 `60 * 1000`을 지정하게 되면, 1분뒤에 가비지 컬렉터가 수집하게 되어 API 데이터가 undefined로 처리됩니다. + +cacheTime의 경우에는 `Infinity`로 지정했는데요, 이는 영구적으로 캐시에 저장하고 있겠다라는 의미입니다. + +만약 cacheTime을 `60 * 1000`을 지정하게 되면, 1분뒤에 가비지 컬렉터가 수집하게 되어 API 데이터가 `undefined`로 처리됩니다. +
+ 위에서 설명드린 `staleTime`과 `cacheTime`의 개념을 가진상태로 이제 본격적인 내용으로 넘어가보겠습니다. ## 2. 차이점 알아보기 📌 ### 2-1. isFetching -isFetching 속성이 true가 되는 조건은 다음과 같습니다. + +`isFetching` 속성이 true가 되는 조건은 다음과 같습니다. + - 캐시에 저장된 데이터가 없거나, cacheTime을 지정한 시간이 지나고나서 재요청이 일어날때 - staleTime을 지정한 시간이 지나서 재요청이 일어날때 -
+ 일반적으로 isFetching 속성은 모든 API 요청이 일어나면 상태가 변경됩니다. ### 2-2. isLoading -isLoading 속성이 true가 되는 조건은 다음과 같습니다. + +`isLoading` 속성이 true가 되는 조건은 다음과 같습니다. + - 캐시에 저장된 데이터가 없거나, cacheTime을 지정한 시간이 지나고나서 재요청이 일어날때 -isLoading 속성은 무조건 *캐시에 데이터가 저장되어 있지 않다면*(cacheTime이 지났다면) true로 변경이 됩니다. +isLoading 속성은 무조건 _캐시에 데이터가 저장되어 있지 않다면_(cacheTime이 지났다면) true로 변경이 됩니다. +
-하지만 isLoading 속성은 사용할때 불편한점이 하나 있는데요, 바로 useQuery의 `enabled` 속성을 사용할때입니다. + +하지만 isLoading 속성은 사용할때 불편한점이 하나 있는데요, 바로 useQuery의 `enabled` 속성을 사용할때입니다. + > **enabled**: 지정한 조건이 참인 경우에만 요청을 수행합니다. 특정 조건에서만 useQuery의 실행이 가능하도록 해주는 `enabled` 속성이 `isLoading` 속성과 무슨 관련이 있을까요? 🤔 코드를 통해서 예시를 보도록 하겠습니다. @@ -60,14 +75,10 @@ import { useQuery } from '@tanstack/react-query'; const fetchTodo = async (todoId: number) => { // return something -} +}; const useFetchTodo = (todoId: number) => { - const { - isLoading, - isFetching, - data, - } = useQuery({ + const { isLoading, isFetching, data } = useQuery({ queryKey: ['some', 'query'], queryFn: fetchTodo, enabled: todoId > 0, @@ -78,19 +89,27 @@ const useFetchTodo = (todoId: number) => { return { data, }; -} +}; useFetchTodo(1); useFetchTodo(-1); ``` + 만약 특정 id를 받아서 요청을하는 기능을 만들때 위와 같이 구현할 수 있습니다. 그리고 저는 `enabled` 속성을 통해서 `todoId` 값이 0보다 클때만 요청이 가능하도록 설정했는데요, 각각 1과 -1을 넘겼을때 실행 결과는 아래와 같습니다. + ```shell isLoading: true, isFetching: true, isLoading: true, isFetching: false, ``` + 올바른 값(1)을 넘겨주었을때는 요청이 정상적으로 되었기에 `isLoading`과 `isFetching` 모두 true가 되었습니다. +
-하지만 올바르지 않은 값(-1)을 넘겼을때는 API 요청은 가지 않았지만, `isLoading` 속성이 true로 찍힘을 알 수 있습니다. 일반적으로 생각해볼때, 만약 요청상황이 중단된다면 로딩상태는 false가 되어야 하는것이 맞습니다. `isFetching`의 동작처럼 말이죠. + +하지만 올바르지 않은 값(-1)을 넘겼을때는 API 요청은 가지 않았지만, `isLoading` 속성이 true로 찍힘을 알 수 있습니다. + +일반적으로 생각해볼때, 만약 요청상황이 중단된다면 로딩상태는 false가 되어야 하는것이 맞습니다. `isFetching`의 동작처럼 말이죠. + 그렇기에 만약 isLoading을 사용하는 개발자가 로딩처리 UI를 구현할때는 아래와 같이 작성해야합니다. ```tsx @@ -99,56 +118,63 @@ const todoId = createRandomNumber(); const { data, isLoading } = useFetchTodo(todoId); if (isLoading && todoId !== -1) { - return + return } return ( // ... ); ``` + 그냥 나는 로딩 상태변수만 사용해서 로딩처리를 하고싶은데, 왜 인자로 전달하는 값까지 불러와서 로딩처리를 해야하는거야?? 하는 불만이 개발자에게 생기기 마련입니다. +
-*그래서!* 이를 해결할 수 있는 또다른 상태 속성이 있는데요, 이것이 바로 `isInitialLoading` 속성입니다. + +_그래서!_ 이를 해결할 수 있는 또다른 상태 속성이 있는데요, 이것이 바로 `isInitialLoading` 속성입니다. ### 2-3. isInitialLoading -isInitialLoading 속성이 true가 되는 조건은 다음과 같습니다. + +`isInitialLoading` 속성이 true가 되는 조건은 다음과 같습니다. + - 캐시에 저장된 데이터가 없거나, cacheTime을 지정한 시간이 지나고나서 재요청이 일어날때 -
-isInitialLoading 속성은 무조건 *캐시에 데이터가 저장되어 있지 않다면* (cacheTime이 지났다면) true로 변경이 됩니다. `isLoading`과 기본적으로 동일하지만, `enabled`의 결과에 따라서 알아서 false로 숨어주는 모습을 갖췄기에 훨씬 더 좋은 속성이라고 생각합니다. -staleTime이 지나서 요청되는건에 대해서는 영향을 끼치지 않습니다. + +isInitialLoading 속성은 무조건 _캐시에 데이터가 저장되어 있지 않다면_ (cacheTime이 지났다면) true로 변경이 됩니다. + +`isLoading`과 기본적으로 동일하지만, `enabled`의 결과에 따라서 알아서 false로 숨어주는 모습을 갖췄기에 훨씬 더 좋은 속성이라고 생각합니다. staleTime이 지나서 요청되는건에 대해서는 영향을 끼치지 않습니다. + > isInitialLoading === (isLoading && isFetching)과 동일한 결과를 가집니다. 코드를 통해 실행결과를 확인해봅시다. + ```typescript import { useQuery } from '@tanstack/react-query'; const fetchTodo = async (todoId: number) => { // return something -} +}; const useFetchTodo = (todoId: number) => { - const { - isLoading, - isInitialLoading, - isFetching, - data, - } = useQuery({ + const { isLoading, isInitialLoading, isFetching, data } = useQuery({ queryKey: ['some', 'query'], queryFn: fetchTodo, enabled: todoId > 0, }); - console.log(`isLoading: ${isLoading}, isInitialLoading: ${isInitialLoading}, isFetching: ${isFetching}`); + console.log( + `isLoading: ${isLoading}, isInitialLoading: ${isInitialLoading}, isFetching: ${isFetching}`, + ); return { data, }; -} +}; useFetchTodo(1); useFetchTodo(-1); ``` + 코드의 실행 결과는 아래와 같습니다. + ```shell isLoading: true, isInitialLoading: true, isFetching: true isLoading: true, isInitialLoading: false, isFetching: false @@ -157,8 +183,13 @@ isLoading: true, isInitialLoading: false, isFetching: false 아까 isLoading 속성이 enabled 속성의 영향을 받으면 올바르게 동작하지 않던 단점을 `isInitialLoading` 속성이 완벽하게 커버 해줍니다. 👍 ## 3. 글을 마치며 😃 + 몇달전부터 React-Query를 사용하면서 기존에 사용하던 SWR보다 더 좋다라는 생각이 자주들만큼, 가장 좋아하는 API 상태관리 라이브러리인듯 합니다. +
+ 이전에 `isLoading` 속성만 알고 사용할때는 로딩처리를 할때 불필요한 조건도 같이 들어갔었는데, 앞으로 `isInitialLoading`을 사용하면 불필요한 코드들이 줄어서 좋을것 같습니다 👍 +
+ 혹시나 글에서 잘못된 부분이 있다면, 피드백 주시면 감사드리겠습니다! 😀 이상으로 글을 마치도록 하겠습니다. 감사합니다. diff --git a/posts/look-around-server-components.mdx b/posts/React/look-around-server-components.mdx similarity index 66% rename from posts/look-around-server-components.mdx rename to posts/React/look-around-server-components.mdx index f7c2805..48f28f0 100644 --- a/posts/look-around-server-components.mdx +++ b/posts/React/look-around-server-components.mdx @@ -1,19 +1,24 @@ --- title: 리액트 서버 컴포넌트, 간단하게 알아보기 description: React 18 버전에서 추가된 서버 컴포넌트에 대해서 간단하게 알아봅시다. -category: React createdAt: 2023-06-14 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/4b796060-af3d-4e0a-82e1-ec5544e2bbc4 +thumbnail: /images/posts/react/look-around-server-components/thumbnail.png --- 안녕하세요! 오늘은 **리액트 서버 컴포넌트**에 대해서 알아보려고 합니다. +
-최근 리액트 생태계에서는 `서버 컴포넌트` 개념이 등장 함으로써 많은 개발자들의 주목을 받고 있더라고요. 저는 서버 컴포넌트에 대해서 정말 대충 알고만 있었는데, 이참에 한번 제대로 알아보면 좋을것 같아서 글 주제로 삼게 되었네요. 😃 +최근 리액트 생태계에서는 `서버 컴포넌트` 개념이 등장 함으로써 많은 개발자들의 주목을 +받고 있더라고요. 저는 서버 컴포넌트에 대해서 정말 대충 알고만 있었는데, 이참에 한번 +제대로 알아보면 좋을것 같아서 글 주제로 삼게 되었네요. 😃 ## 1. 서버 컴포넌트란? 🤔 + 리액트 `서버 컴포넌트`는 **리액트 18 버전**에서 추가된 기능이며, 서버 측에서 컴포넌트를 실행하여 미리 렌더링된 마크업을 생성후, 클라이언트에게 전달되어 사용자에게 콘텐츠를 보여주는 방식입니다. +
-기존에 `Next.js`에서는 Page 단위의 컴포넌트에서만 백엔드 리소스에 접근이 가능했지만, `RSC`를 사용하게되면 일반적인 단위의 컴포넌트에서 접근 가능하다는 특징이 존재합니다. +기존에 `Next.js`에서는 Page 단위의 컴포넌트에서만 백엔드 리소스에 접근이 가능했지만, +`RSC`를 사용하게되면 일반적인 단위의 컴포넌트에서 접근 가능하다는 특징이 존재합니다. > **백엔드 리소스**: 데이터베이스, fs, node-fetch 등의 서버에서 접근 가능한 리소스 @@ -24,15 +29,14 @@ import db from 'db'; // 서버 컴포넌트 예제 const ServerComponent = () => { const file = fs.readFileSync('../'); // 파일 시스템 접근 가능 - const data = db.post.load(); // 데이터베이스 접근 가능 + const data = db.post.load(); // 데이터베이스 접근 가능 - return ( -
- ); -} + return
; +}; ``` 서버에서 렌더링된 컴포넌트는 **직렬화(Serialization)** 가능한 상태로 클라이언트에게 데이터가 전달되는데요. 이때 함수(Function), 날짜(Date) 등의 데이터는 직렬화 불가능한 데이터로 취급되므로 `서버 컴포넌트`에서 작성할 수 있는 코드가 제한되어 있다는 특징이 있습니다. +
대표적으로 서버 컴포넌트에서 작성할 수 없는 코드들은 아래와 같습니다. @@ -45,61 +49,94 @@ const ServerComponent = () => { ## 2. 서버 컴포넌트의 이점 🔍 ### 2-1. 환경별 리소스 접근 + 환경별 리소스에 접근하기 좋다는 말은 위에서 설명드린 `서버 컴포넌트`의 특징 중 하나인 백엔드 리소스에 접근이 가능하다는 특징도 해당되는 개념인데요, 이를 조금 더 자세하게 설명드리려고 합니다. +
-서버 컴포넌트의 **백엔드 리소스 접근**에 관한 특징은 위에서 말씀 드렸으므로, `클라이언트 컴포넌트`의 특징에 대해서 알아보겠습니다. +서버 컴포넌트의 **백엔드 리소스 접근**에 관한 특징은 위에서 말씀 드렸으므로, `클라이언트 +컴포넌트`의 특징에 대해서 알아보겠습니다. ```tsx const SomeComponent = () => { const userInfo = localStorage.getItem('user'); // 서버에서 렌더링될경우 오류 발생 - return ( -
- ); -} + return
; +}; ``` 만약 Next.js에서 컴포넌트를 제작하던 중, 해당 컴포넌트에서 `로컬스토리지` 혹은 `쿠키` 등 브라우저에서만 접근이 가능한 데이터를 사용하려고 했던 경험들이 있으실겁니다. 이런 경우에는 해당 컴포넌트를 `dynamic import`를 사용하여 서버에서 렌더링 되지 않도록 비활성화 해야합니다. 만약 이 컴포넌트가 여러곳에서 사용되는 컴포넌트라면, 일일히 `dynamic import`를 적용해줘야 하므로 귀찮아지죠. 😥 +
-그래서! 이 경우에는 리액트 18에서 `서버 컴포넌트`와 같이 출시된 `클라이언트 컴포넌트`로 정의하여 사용하면, 해당 컴포넌트는 무조건 클라이언트에서만 렌더링되는 컴포넌트이므로 복잡하게 브라우저에서만 사용가능한 API를 편하게 사용할 수 있습니다. +그래서! 이 경우에는 리액트 18에서 `서버 컴포넌트`와 같이 출시된 `클라이언트 컴포넌트`로 +정의하여 사용하면, 해당 컴포넌트는 무조건 클라이언트에서만 렌더링되는 컴포넌트이므로 +복잡하게 브라우저에서만 사용가능한 API를 편하게 사용할 수 있습니다.
-기존에 처리하기 번거로웠던 백/브라우저 API 사용에 대해서 각각 서버/클라이언트 컴포넌트를 사용하여 해결할 수 있을것 같네요. 😃 +기존에 처리하기 번거로웠던 백/브라우저 API 사용에 대해서 각각 서버/클라이언트 컴포넌트를 +사용하여 해결할 수 있을것 같네요. 😃{' '} ### 2-2. 제로 번들 컴포넌트 + 기존에 모든 리액트 컴포넌트는 **브라우저에서 다운로드** 됩니다. 그러나 만약 해당 컴포넌트가 방대하거나, 외부 라이브러리를 많이 사용하게되면 컴포넌트의 번들 사이즈는 계속 증가됩니다. `코드 스플리팅`으로 이를 최적화 할 수는 있지만, 어쨋거나 번들 사이즈가 커지게되면 사용자에게 보이는데 까지 오랜 시간이 걸릴수도 있지요. ```json { // 서버 -> 클라이언트로 전달되는 JSON 직렬화 데이터 - "A1":{"id":"./src/ClientComponent.tsx","chunks":["client1"],"name":""}, - "C0":["$","@1",null,{"children":["$","span",null,{"children":"Hello! I'm Client Component!"}]}] + "A1": { + "id": "./src/ClientComponent.tsx", + "chunks": ["client1"], + "name": "" + }, + "C0": [ + "$", + "@1", + null, + { + "children": [ + "$", + "span", + null, + { "children": "Hello! I'm Client Component!" } + ] + } + ] } ``` 하지만 `서버 컴포넌트`는 해당 컴포넌트의 코드 및 번들이 **서버에서 다운로드** 되고, 번들이 브라우저로 전송이 되지 않습니다. 위에서 설명드린것처럼 **직렬화(Serialization)** 가능한 상태로 클라이언트에게 JSON 데이터를 전달하여 렌더링하는 방식이기 때문이죠. `제로 번들 사이즈`의 특징을 지녔기때문에 기존보다 훨씬 빠르게 사용자는 컴포넌트를 볼 수 있을것 같습니다. 👍 ### 2-3. 서버 사이드 렌더링과의 차이점 + 저는 처음에 서버 컴포넌트는 **서버 사이드 렌더링**과는 어떤점이 다른걸까? 하는 궁금증이 많이 생겼는데요, 그래서 관련 자료들을 찾아본 결과 아래의 특징으로 정리해볼 수 있었습니다. > **1. 서버 컴포넌트는 클라이언트에게 전달되지 않는다.** -기본적인 서버 사이드 렌더링은 **JS 번들**이 클라이언트에게 전달되지만, 서버 컴포넌트를 사용하게 되면 **컴포넌트 JS 번들**이 전달되지 않는다. (UX 향상) +> 기본적인 서버 사이드 렌더링은 **JS 번들**이 클라이언트에게 전달되지만, 서버 컴포넌트를 사용하게 되면 **컴포넌트 JS 번들**이 전달되지 않는다. (UX 향상) > **2. 컴포넌트 단계에서 백엔드 리소스 접근이 가능하다.** -기존에 Next.js에서는 최상위 단계(pages) 컴포넌트의 **getInitialProps**, **getServerSideProps** 함수에서만 접근이 가능했지만, 서버 컴포넌트를 사용하면 어느 단계에서든 접근이 가능하다. +> 기존에 Next.js에서는 최상위 단계(pages) 컴포넌트의 **getInitialProps**, **getServerSideProps** 함수에서만 접근이 가능했지만, 서버 컴포넌트를 사용하면 어느 단계에서든 접근이 가능하다. > **3. 리렌더링 방식이 다르다.** -서버 사이드 렌더링은 HTML 문서 자체를 업데이트 해야만 데이터를 최신화 하여 리렌더링이 가능하지만, 서버 컴포넌트는 클라이언트 상태(State)를 유지하면서 리렌더링 할 수 있다. +> 서버 사이드 렌더링은 HTML 문서 자체를 업데이트 해야만 데이터를 최신화 하여 리렌더링이 가능하지만, 서버 컴포넌트는 클라이언트 상태(State)를 유지하면서 리렌더링 할 수 있다. ### 3-1. 추가적인 내용 🖋️ + `서버 사이드 렌더링`의 기본 개념인 **서버에서 HTML 콘텐츠를 채워서 클라이언트에게 전달한다**의 단계는 서버 컴포넌트가 렌더링 되기 전에 수행됩니다. +
-그래서 제가 추가적으로 들었던 생각은 `서버 컴포넌트`가 동작하기 위해선 `서버 사이드 렌더링`이 필요로 하며, **서버 사이드 렌더링의 보완 및 최적화 수단** 느낌이 들었습니다. (서버 사이드 렌더링 안에 서버 컴포넌트가 포함되는 관계이지 않을까..) +그래서 제가 추가적으로 들었던 생각은 `서버 컴포넌트`가 동작하기 위해선 `서버 사이드 +렌더링`이 필요로 하며, **서버 사이드 렌더링의 보완 및 최적화 수단** 느낌이 들었습니다. +(서버 사이드 렌더링 안에 서버 컴포넌트가 포함되는 관계이지 않을까..) ## 4. 마치며 📌 + 지금까지 리액트의 `서버 컴포넌트`의 정의, 특징에 대해서 알아보았는데요. 저는 지금까지 서버 컴포넌트를 다뤄본 경험이 없다보니, 자료들을 이해하는데 어려움이 많았던것 같습니다. 😥 +
-최근에 출시된 [Next.js 13 버전](https://nextjs.org/docs/app/building-your-application/routing#the-app-router)의 `app directory`를 사용하면 서버 컴포넌트를 손쉽게 사용 가능하더라고요! 아직까지는 회사에서 Next.js 12 버전을 주로 사용하는데, 13 버전은 언제 넘어갈지는 아직 잘 모르겠네요. -프로덕에서 사용하는데 매우 안정화된 상태가 되면 한번 찍먹해보지 않을까 싶습니다. 🤔 +최근에 출시된 [Next.js 13 버전](https://nextjs.org/docs/app/building-your-application/routing#the-app-router)의 +`app directory`를 사용하면 서버 컴포넌트를 손쉽게 사용 가능하더라고요! 아직까지는 +회사에서 Next.js 12 버전을 주로 사용하는데, 13 버전은 언제 넘어갈지는 아직 잘 모르겠네요. +프로덕에서 사용하는데 매우 안정화된 상태가 되면 한번 찍먹해보지 않을까 싶습니다. +🤔
-이상으로 긴 글 읽어주셔서 감사합니다! 혹여나 글에서 잘못된 부분이 있다면, `피드백` 남겨주시면 감사하겠습니다. 😛 \ No newline at end of file +이상으로 긴 글 읽어주셔서 감사합니다! 혹여나 글에서 잘못된 부분이 있다면, `피드백` +남겨주시면 감사하겠습니다. 😛 diff --git a/posts/thought-typescript-implict-this.mdx b/posts/TypeScript/thought-typescript-implict-this.mdx similarity index 93% rename from posts/thought-typescript-implict-this.mdx rename to posts/TypeScript/thought-typescript-implict-this.mdx index 79652a4..7f7a30b 100644 --- a/posts/thought-typescript-implict-this.mdx +++ b/posts/TypeScript/thought-typescript-implict-this.mdx @@ -1,17 +1,19 @@ --- title: 타입스크립트의 this 바인딩에 대한 고찰 description: 타입스크립트의 암시적 바인딩과 관련한 개발 중 이슈 및 이에대한 고찰을 정리합니다. -category: TypeScript createdAt: 2023-05-18 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/8e018cdb-a7cc-41c7-9eee-5c6e1eb6e64c +thumbnail: /images/posts/typescript/thought-typescript-implict-this/thumbnail.png --- 안녕하세요! 오늘은 타입스크립트의 암시적 바인딩과 관련한 개발 중 이슈 및 이에대한 고찰에 대해서 정리해보려고 합니다. TIL 느낌으로 작성해보았습니다. ## 1. 개요 🧪 + 며칠전 회사에서 프로젝트 개발(프론트엔드)을 여느 때와 같이 하고 있었다. 그중에서 API 통신쪽 코드를 작성하고 있었는데, 나의 코드 구조는 API 주소 및 axios 형식을 정리해둔 `Repository` 파일 -> react-query를 사용하여 데이터를 받아오는 `Custom hooks` 파일 -> 받은 데이터를 렌더링 하는 `Component` 파일 순서로 이루어진다. +
+ 예를 들어, Repository 파일 형식은 아래와 같다. ```typescript @@ -37,23 +39,24 @@ export const postRepository = new PostRepository(); ``` 원래 각 메소드마다 클래스 멤버 변수를 사용하지 않고 url을 지정했었는데, **이 도메인의 API들은 대부분 주소가 비슷한 거 같아서 한번 멤버 변수로 사용해 보려고 했다.** +
+ 그리고 커스텀 훅 파일은 아래와 같이 작성했다. 그중에서 fetchPosts 메소드를 사용하는 커스텀 훅이다. + ```typescript import { useQuery } from '@tanstack/react-query'; import { postCacheKey } from '@/libs/models/query/post.ts'; import { postRepository } from '@/repositories/post'; const useFetchPosts = () => { - const { - data, - } = useQuery({ + const { data } = useQuery({ queryKey: postCacheKey.posts, queryFn: postRepository.fetchPosts, }); // ... -} +}; ``` 이제 커스텀 훅도 다 적었겠다, 나는 이제 커스텀 훅을 컴포넌트에서 렌더링 하기위해서 호출하였는데 예상하지 못한 오류가 있었다. 😲 @@ -63,7 +66,7 @@ class PostRepository { private readonly BASE_URL = '/posts'; async fetchPosts(): Promise { - console.log(this.BASE_URL) // undefined + console.log(this.BASE_URL); // undefined const { data } = await commonAxios.get(this.BASE_URL); return data; @@ -74,67 +77,80 @@ class PostRepository { 그것은 바로, 중복 코드를 줄이기 위해서 클래스 멤버 변수로 선언된 `BASE_URL`의 값이 **undefined로 출력되는 문제**였다. 😥 ## 2. 해결방법 먼저! 📘 + 해결 방법은 두가지 중 하나를 선택하여 해결할 수 있었다. +
+ 첫번째는 `fetchPosts` 메소드를 **화살표 함수로 선언** 하는것이다. + ```typescript class PostRepository { private readonly BASE_URL = '/posts'; fetchPosts = async (): Promise => { - console.log(this.BASE_URL) // '/posts' 정상출력 + console.log(this.BASE_URL); // '/posts' 정상출력 const { data } = await commonAxios.get(this.BASE_URL); return data; - } + }; } ``` 두번째는 `useQuery` hooks의 인자 queryFn 속성에다가 **새로운 함수를 생성하여 넘기는 방식**을 통하여 해결이 가능하다. + ```typescript -const { - data, -} = useQuery({ +const { data } = useQuery({ queryKey: postCacheKey.posts, queryFn: () => postRepository.fetchPosts(), }); ``` + 혹은 아래의 방법으로 **함수를 선언하여** 넘기는것도 가능하다. + ```typescript const fetchPosts = async () => { return await postRepository.fetchPosts(); -} +}; -const { - data, -} = useQuery({ +const { data } = useQuery({ queryKey: postCacheKey.posts, queryFn: fetchPosts, }); ``` + ## 3. 그런데 원인이 무엇일까? 🤔 + 이슈는 해결했지만, 나는 당연히 동작할것이라고 예상했던 코드가 동작하지 않았던 점에 대해서 궁금증이 생겼다. this를 사용한 멤버 변수 사용에서 문제가 생겼기에 나는 **this 바인딩**과 관련해서 문제가 있을것이다고 생각했고, 타입스크립트의 this 바인딩에 대해서 한번 더 공부했다. ### 3-1. 함수 선언 방식에 따른 this 바인딩 + JavaScript에서 함수를 선언하는 두 가지 방식인 일반 함수와 화살표 함수는 this 바인딩에 대해 서로 다른 동작을 한다. this 바인딩은 함수가 어떤 객체를 참조하는지를 결정하는 중요한 개념이다. +
+ 일반 함수는 `function` 키워드로 선언되며, 함수 내에서 this 키워드를 사용하면 현재 실행 중인 메서드나 함수가 속한 객체를 참조하게된다. ```typescript const myObject = { name: 'John', - sayHello: function() { + sayHello: function () { console.log('Hello, ' + this.name); }, }; myObject.sayHello(); // 출력: "Hello, John" ``` + 위 예시에서 `myObject` 객체의 `sayHello` 메서드를 호출하면 this는 `myObject`를 참조한다. 따라서 this.name은 `myObject` 객체의 `name` 속성 **John을 출력**합니다. +
+ 화살표 함수는 ES6에서 도입된 새로운 함수 선언 방식이고, `=>` 기호를 사용하여 선언한다. +
+ 일반 함수와 달리 자체적인 this를 갖지 않는다. 대신, 화살표 함수는 함수가 선언된 위치에서 상위 스코프로부터 this가 정해진다. ```typescript @@ -148,14 +164,21 @@ const myObject = { myObject.sayHello(); // 출력: "Hello, undefined" ``` + 위 예시에서 화살표 함수인 `sayHello` 내부의 this.name은 `myObject` 객체의 name 속성을 참조하지 않는다. 그리고, 상위 스코프인 전역 스코프의 this를 상속받는데, 전역 스코프에는 name 속성이 정의되어 있지 않으므로 undefined가 출력된다. +
+ 위에서 알아본 this 바인딩의 특징들을 통해서 1번에서 마주친 이슈에 대해서 분석해보자. ### 3-2. 암시적 바인딩 + 타입스크립트의 `암시적 바인딩`이란 this를 결정하는 방법 중 하나이며, 함수가 호출될때 함수의 주변 컨텍스트를 기반으로 this를 바인딩 하는 개념이고, 이때 `주변 컨텍스트`란 함수가 호출되는 시점에서 해당 함수를 감싸는 객체 혹은 메소드이다. +
+ 간단한 예시로, 아래의 코드를 실행하면 결과는 다음과 같다. + ```typescript const myObject = { name: 'John', @@ -169,9 +192,14 @@ myObject.sayHello(); // Hello, John ``` 그러나 암시적 바인딩을 사용했을때 this로 가리킨 객체의 값이 변할 수 있다는 것인데, 이 문제는 메소드를 콜백함수로 넘기거나, 변수에 메소드를 할당하는 경우에 발생한다. +
+ 이와 관련해서 나는 **왜 할당연산 등의 동작이 일어났을때 this가 손실되는걸까?** 하는 궁금증이 생겼고, 알아본결과 아래와 같은 결론을 얻을 수 있었다. -> 할당연산과 같은 동작의 특징때문인데 **참조 타입이 통째로 버려지고, 값만 함수에게 전달하기 때문이다.** 참조 타입이 날아간다는 의미는 this 객체가 사라진다는 의미이고, 코드 실행에 따른 동적으로 this가 결정되는 일반함수에게는 위험요소가 따르게 된다. + +> 할당연산과 같은 동작의 특징때문인데 **참조 타입이 통째로 버려지고, 값만 함수에게 전달하기 때문이다.** +> 참조 타입이 날아간다는 의미는 this 객체가 사라진다는 의미이고, 코드 실행에 따른 동적으로 +> this가 결정되는 일반함수에게는 위험요소가 따르게 된다. ```typescript const myObject = { @@ -206,6 +234,9 @@ setTimeout(myObject.sayHello, 500); // TypeError: this.name is undefined 1번 글 섹션에서 미리 얘기했던 것처럼, 위와 같은 문제를 해결하려면 **화살표 함수**를 사용하거나 `명시적 바인딩` (call, apply, bind)을 사용하여 this가 변경되는 문제를 해결할 수 있다. ## 4. 마치며 📌 + 오늘은 개발중에 마주친 this 바인딩 문제에 대해서 정리한 글을 작성해보았는데요, 예전에 이론상으로 공부한적이 있는 this 바인딩이였지만 실제로 마주쳐보니 정확한 원인을 찾기가 어려웠네요. 다음부터 일반함수를 사용할때는 this 키워드 사용에 유의해야겠습니다. +
-이상으로 글을 마치겠습니다. 긴 글 읽어주셔서 감사합니다! \ No newline at end of file + +이상으로 글을 마치겠습니다. 긴 글 읽어주셔서 감사합니다! diff --git a/posts/tsconfig-options.mdx b/posts/TypeScript/tsconfig-options.mdx similarity index 87% rename from posts/tsconfig-options.mdx rename to posts/TypeScript/tsconfig-options.mdx index 2d690b1..4d361d6 100644 --- a/posts/tsconfig-options.mdx +++ b/posts/TypeScript/tsconfig-options.mdx @@ -1,28 +1,37 @@ --- title: tsconfig.json에서 사용되는 옵션 정리 description: 타입스크립트 프로젝트에서 사용되는 tsconfig.json 파일의 옵션들에 대해서 정리해봅시다. -category: TypeScript createdAt: 2023-05-24 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/3aebd18b-a311-4610-9a23-75253e1f1df1 +thumbnail: /images/posts/typescript/tsconfig-options/thumbnail.png --- + 안녕하세요! 오늘은 타입스크립트 프로젝트에서 사용되는 `tsconfig.json` 파일의 옵션들에 대해서 정리해보려고 합니다. +
+ 타입스크립트를 사용하는 프론트엔드 혹은 백엔드 개발자분들은 `tsconfig.json` 파일을 누구나 한번쯤 보고, 만져보셨을겁니다. 프로젝트를 컴파일 하는데에 중요한 역할을 담당하고 있는 설정파일이기 때문이에요. + `tsconfig.json` 파일을 많이 보신분들은 알겠지만, 컴파일과 관련한 타입스크립트의 옵션들은 정말 많습니다. 이를 다 외우기엔 옵션도 많고, `이 옵션이 정확히 어떤 역할을 하는거였더라?` 하는 생각도 저는 많이 들더라고요. +
+ 그래서! 오늘은 핵심적인 `tsconfig.json` 파일의 옵션들에 대해서 정리해보겠습니다. ## 1. 전역 속성 📘 + `tsconfig.json` 파일에서 전역 속성이란, **최상위에 위치하고 있는 속성**을 뜻하는데요, 대표적으로 `include`, `exclude`, `extends`, `compilerOptions` 등이 있습니다. 그래서 총 4개의 전역 속성들에 대해서 알아보겠습니다. ### 1-1. include + `include`라는 단어는 `포함하다`라는 뜻을 가지고 있죠? include 속성은 **타입스크립트 프로젝트에서 컴파일할 파일들을 지정하는 속성**이며, 배열안에 여러개의 파일을 넣을 수 있습니다. +
+ 그러나 만약 프로젝트의 규모가 커져버려서 파일이 많이 생겨버렸을때는 어떻게 해야할까요? 일일히 파일을 다 적기엔 시간도 많이 들고 비효율적이죠. 이를 해결하기위해서 `include` 속성은 **와일드 카드** 패턴을 사용 할 수 있습니다. > 와일드 카드 패턴에 대해서는 아래 글을 읽어보시면 많은 도움이 될겁니다. 😃 -[https://www.lifencoding.com/web/21?p=1](https://www.lifencoding.com/web/21?p=1) +> [https://www.lifencoding.com/web/21?p=1](https://www.lifencoding.com/web/21?p=1) ```json { @@ -36,8 +45,11 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/3aebd18b-a311-4 제가 많이 사용하는 React, Next 타입스크립트 프로젝트를 생성하면 기본적으로 `include` 속성에는 **ts**, **tsx** 확장자를 컴파일 하겠다라고 정의가 되어있어서, 이를 직접 건드려본적은 많이 없었던것 같네요 😛 ### 1-2. exclude + `exclude` 옵션은 와일드 카드 패턴을 지원함으로써 `include` 옵션과 사용법은 같으나, 특징이 완전히 반대입니다. 즉 `exclude`는 타입스크립트 컴파일을 제외할 파일 목록을 설정하는 옵션이죠. +
+ 흔히 React, Next로 만든 프로젝트의 `exclude` 옵션을 살펴보시면 아래와 같이 되어있어요. ```json @@ -49,17 +61,14 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/3aebd18b-a311-4 ``` ### 1.3 extends + `extends` 옵션은 컴파일하고는 관계가 조금 멀다고 해야할까요? 대신 `tsconfig.json` 파일을 작성하면서 옵션들을 다른 JSON 파일로부터 `extends` 할 수 있다는 특징이 있습니다. 아래 코드를 통해서 예시를 보겠습니다. ```json // base.json { - "include": [ - "**/*.ts" - ], - "exclude": [ - "node_modules" - ], + "include": ["**/*.ts"], + "exclude": ["node_modules"], "compilerOptions": { "allowJs": true } @@ -77,14 +86,19 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/3aebd18b-a311-4 ``` 위와 같이 `tsconfig.json` 파일에서 `extends` 키워드로 속성 상속시, 해당 `tsconfig.json` 파일에서는 상속받은 옵션을 정의할 필요없이 사용할 수 있습니다. + `tsconfig.json`에서는 기본적으로 `include`, `exclude`, `compilerOptions.allowJs` 속성을 사용중인거죠. ## 2. compilerOptions 🧪 + `compilerOptions` 속성또한 위에서 설명드린 **전역 속성**에 들어가는 속성이지만, `compilerOptions`안에는 더 많은 속성들이 존재하므로 따로 섹션을 구분했습니다. 제가 느끼기로는 compilerOptions 안에 존재하는 속성들이 다른 `전역 속성`들의 수만큼 존재하는거 같아요.. 🤔 +
+ 이 수많은 **compilerOptions**중에서 몇개의 중요한 속성만 알아보겠습니다! ### 2-1. target + 흔히 타입스크립트의 컴파일 과정은 간단하게 아래와 같습니다. - 타입스크립트 소스코드가 **추상 문법트리 (Abstract Syntax Tree)로 변환**된다. @@ -92,6 +106,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/3aebd18b-a311-4 - 타입스크립트 추상 문법트리를 **자바스크립트 코드**로 변환한다. 위처럼 3번 과정에서 자바스크립트 코드로 변환되어서 나머지 과정이 처리되는데요, **어떤 자바스크립트 형식으로 변환될지를 결정하는 옵션**이 `target` 옵션입니다. + > 자바스크립트의 ECMAScript의 종류는 여러가지가 있죠. es5, es6, es2015, es2016, esnext 등의 형식으로 적을 수 있습니다. ```json @@ -103,28 +118,33 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/3aebd18b-a311-4 ``` 하지만 타입스크립트 소스코드에 `target`에 지정한 ECMAScript가 지원하지 않는 문법이 적힌경우, 컴파일 오류가 발생하기에 이를 유의해야합니다. 예를 들어 `target`을 **es5**로 지정을 했는데, 타입스크립트 코드에서 Promise, async-await을 사용하면 오류가 발생하므로 유의해야합니다. +
+ 보통은 ES6를 많이들 사용하는거 같아요. 😃 ### 2-2. lib + `lib` 옵션은 타입스크립트 컴파일에 필요한 자바스크립트의 내장 라이브러리를 지정할 수 있는데요, 대표적인 예로 `DOM API`, `Geolocation API`, `Intersection Observer API` 등등 다양하게 있습니다. +
-만약 lib 옵션이 지정되어있지 않다면, target 옵션에 지정된 ECMAScript 버전에 따라서 컴파일이 필요한 라이브러리가 자동으로 지정됩니다. `target`이 ES6로 지정된경우, ES6 컴파일에 필요한 라이브러리가 일부 기본으로 지정됩니다. + +만약 lib 옵션이 지정되어있지 않다면, target 옵션에 지정된 ECMAScript 버전에 따라서 컴파일이 필요한 라이브러리가 자동으로 지정됩니다. + +`target`이 ES6로 지정된경우, ES6 컴파일에 필요한 라이브러리가 일부 기본으로 지정됩니다. ```json { "compilerOptions": { - "lib": [ - "dom", - "dom.iterable", - "esnext" - ] + "lib": ["dom", "dom.iterable", "esnext"] } } ``` ### 2-3 allowJs + `allowJs` 옵션은 타입스크립트 프로젝트에서 자바스크립트 파일을 사용할 수 있는지를 설정할 수 있는 옵션입니다. `allowJs`를 `true`로 설정하게 되면, 타입스크립트 파일에서 자바스크립트 파일을 import 하여 사용가능합니다. + 기본값은 `false` 입니다. ```json @@ -136,10 +156,11 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/3aebd18b-a311-4 ``` ### 2-4. jsx + `jsx` 옵션은 .tsx 확장자의 타입스크립트 파일을 **어떻게 컴파일 할것인가**를 지정하는 옵션입니다. 사용할 수 있는 옵션은 아래와 같습니다. - react: .js 파일로 컴파일 (JSX 코드는 **React.createElement()** 함수 호출로 처리됨) -- react-jsx: .js 파일로 컴파일 (JSX 코드는 **_jsx()** 함수 호출로 처리됨) +- react-jsx: .js 파일로 컴파일 (JSX 코드는 **\_jsx()** 함수 호출로 처리됨) - react-native: .js 파일로 컴파일 (JSX 코드는 유지) - preserve: .jsx 파일로 컴파일 (JSX 코드는 유지) @@ -152,8 +173,11 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/3aebd18b-a311-4 ``` ### 2-5. Decorators + 타입스크립트 프로젝트에서 데코레이터(@Decorator)를 사용하기 위해서는 `experimentalDecorators`, `emitDecoratorMetadata` 두개의 속성을 `true`로 지정해야 사용할 수 있습니다. +
+ `experimentalDecorators`를 지정하면 **컴파일 과정**에서 데코레이터 오류가 발생하지 않고, `emitDecoratorMetadata`를 지정하면 **런타임**에서 올바르지 않게 작동하는 상황을 방지합니다. ```json @@ -166,7 +190,8 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/3aebd18b-a311-4 ``` ### 2-6. module -`module` 옵션은 타입스크립트 프로젝트에서 사용되는 모듈 시스템을 지정하는 옵션이다. + +`module` 옵션은 타입스크립트 프로젝트에서 사용되는 모듈 시스템을 지정하는 옵션입니다. - CommonJS: `target` 옵션이 ES5 이하로 지정된경우 기본값 (require 구문으로 import 처리) - 그외 es6, es2020, exnext (import 구문으로 import 처리) @@ -180,22 +205,24 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/3aebd18b-a311-4 ``` ### 2-7. typeRoots -만약 외부 라이브러리를 사용하는데, 해당 라이브러리에 타입이 정의된 파일이 없다면 `.d.ts` 확장자 파일을 통해서 직접 타입을 정의해야한다. + +만약 외부 라이브러리를 사용하는데, 해당 라이브러리에 타입이 정의된 파일이 없다면 `.d.ts` 확장자 파일을 통해서 직접 타입을 정의해야합니다. +
-이때 `typeRoots` 옵션을 통해서 해당 `.d.ts` 파일의 디렉토리명을 적어주면 **외부 라이브러리 타입정의 파일로 인식**이 가능하다. 그러나 `include` **옵션에 정의된 경로가 포함되어 있다면**, 안적어줘도 상관없다. + +이때 `typeRoots` 옵션을 통해서 해당 `.d.ts` 파일의 디렉토리명을 적어주면 **외부 라이브러리 타입정의 파일로 인식**이 가능합니다. 그러나 `include` **옵션에 정의된 경로가 포함되어 있다면**, 안적어도 됩니다. + ```json { "compilerOptions": { - "typeRoots": [ - "node_modules/@types", - "@types" - ] + "typeRoots": ["node_modules/@types", "@types"] } } ``` ### 2-8. isolatedModules -`isolatedModules` 옵션은 타입스크립트 파일을 모듈로 정의할것을 강제화한다. + +`isolatedModules` 옵션은 타입스크립트 파일을 모듈로 정의할것을 강제화합니다. ```json { @@ -205,11 +232,13 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/3aebd18b-a311-4 } ``` -만약 특정 타입스크립트 파일(예: a.ts)을 만들고나서, export 키워드를 사용하지 않으면 빨간줄이 그어져 컴파일이 실패된다. +만약 특정 타입스크립트 파일(예: a.ts)을 만들고나서, export 키워드를 사용하지 않으면 빨간줄이 그어져 컴파일이 실패됩니다. ### 2-9. esModuleInterop -ES6의 모듈 내보내기시 `export` 혹은 `export default` 형식으로 내보내는데, CommonJs의 `module.exports` 문법이 import와 호환이 되지 않는다. -이와 같은 문제를 해결하기 위해서 `esModuleInterop` 옵션을 지원한다. + +ES6의 모듈 내보내기시 `export` 혹은 `export default` 형식으로 내보내는데, CommonJs의 `module.exports` 문법이 import와 호환이 되지 않습니다. + +이와 같은 문제를 해결하기 위해서 `esModuleInterop` 옵션을 지원합니다. ```json { @@ -228,9 +257,12 @@ import React from 'react'; ``` ### 2-10. resolveJsonModule -`resolveJsonModule` 옵션은 확장자가 .json인 모듈의 import를 허용할 수 있는 옵션이다. 기존 자바스크립트 프로젝트에서는 json import가 아무 문제없이 됐었지만, 타입스크립트에서는 기본적으로 불가능하다. + +`resolveJsonModule` 옵션은 확장자가 .json인 모듈의 import를 허용할 수 있는 옵션입니다. 기존 자바스크립트 프로젝트에서는 json import가 아무 문제없이 됐었지만, 타입스크립트에서는 기본적으로 불가능합니다. +
-이와 같은 문제를 마주칠시에 `resolveJsonModule`의 옵션을 지정해주면, json import가 가능할 뿐더러, 타입까지 자동으로 지정해줌으로써 도움이 된다. + +이와 같은 문제를 마주칠시에 `resolveJsonModule`의 옵션을 지정해주면, json import가 가능할 뿐더러, 타입까지 자동으로 지정해줌으로써 도움이 됩니다. ```json { @@ -241,8 +273,13 @@ import React from 'react'; ``` ## 3. 마치며 📌 + 오늘은 tsconfig 옵션을 모두 정리해본 시간이 되었습니다. 도움이 많이 되셨을까요? 😛 +
-제가 정리한 옵션들 말고도, 수많은 tsconfig 옵션들이 더 많이 있습니다!(자잘한 속성 혹은 조금 중요한 속성) 이번 글에 정리를 다 하려기엔 시간이 부족할거 같아서 제가 자주 찾아보는 속성들만 정리해봤어요. 앞으로 중요한 `tsconfig` 속성들은 항상 머리속에 기억해두겠습니다 😛 + +제가 정리한 옵션들 말고도, 수많은 tsconfig 옵션들이 더 많이 있습니다!(자잘한 속성 혹은 조금 중요한 속성) 이번 글에 정리를 다 하려기엔 시간이 부족할거 같아서 제가 자주 찾아보는 속성들만 정리해봤어요. 앞으로 중요한 `tsconfig` 속성들은 항상 머리속에 기억해두겠습니다. 😛 +
-이상으로 글을 마치겠습니다. 긴 글 읽어주셔서 감사합니다! \ No newline at end of file + +이상으로 글을 마치겠습니다. 긴 글 읽어주셔서 감사합니다! diff --git a/posts/typescript-convariance-and-contravariance.mdx b/posts/TypeScript/typescript-convariance-and-contravariance.mdx similarity index 96% rename from posts/typescript-convariance-and-contravariance.mdx rename to posts/TypeScript/typescript-convariance-and-contravariance.mdx index e240d31..cd36413 100644 --- a/posts/typescript-convariance-and-contravariance.mdx +++ b/posts/TypeScript/typescript-convariance-and-contravariance.mdx @@ -1,16 +1,18 @@ --- title: 타입스크립트의 공변성과 반공변성 description: 타입스크립트의 타입 할당관계 규칙인 공변성과 반공변성에 대해서 알아봅시다. -category: TypeScript createdAt: 2023-09-07 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/04a253ab-fd5e-4d7e-b9e0-7672e20b3be2 +thumbnail: /images/posts/typescript/typescript-convariance-and-contravariance/thumbnail.png --- 안녕하세요! 오늘은 타입스크립트의 타입 할당관계 규칙인 `공변성`과 `반공변성`에 대해서 무엇인지 알아보겠습니다.
+ 처음엔 두 단어를 듣고 생소한 개념인줄 알았는데, 타입스크립트를 사용할때 간간히 마주칠 수 있는 상황이더라고요. 그래서 두 개념에 대해서 적어보려고 합니다! ## 1. 공변성 📕 + `공변성`은 **A라는 타입이 B 타입의 서브타입이면, A는 B 타입의 서브타입이다**는 개념입니다. 공변성 규칙은 **함수의 매개변수**로 전달되는 경우를 제외한 모든 할당규칙에 적용됩니다. + 정말 쉬운 예제로 알아볼까요? ```typescript @@ -27,6 +29,7 @@ yourArray = myArray; // 성공 따라서 **yourArray** 배열의 타입이 **myArray** 배열의 상위 타입이므로 할당 과정에서 오류가 발생하지 않게됩니다. 반대로 서브타입의 배열이 상위 타입의 배열을 할당하려고 하면 오류가 발생하죠. ## 2. 반공변성 📘 + `반공변성`은 **A라는 타입이 B 타입의 서브타입이면, B는 A타입의 서브타입이다**는 개념입니다. 처음들었을때는 이게 뭔 소리인가 싶었습니다. 말장난 하는 것 처럼요. 😟
`반공변성`은 **함수의 매개변수**로 전달되는 경우에만 적용되는 규칙입니다. 앞서 설명드렸던 `공변성`과는 완전 반대의 특징이죠? @@ -37,11 +40,11 @@ type BaseFunction = (params: T) => void; let myFunction: BaseFunction = (param) => { // do something -} +}; let yourFunction: BaseFunction = (param) => { // do something -} +}; myFunction = yourFunction; // 성공 yourFunction = myFunction; // 에러 @@ -53,6 +56,7 @@ yourFunction = myFunction; // 에러 함수의 매개변수로 타입규칙이 전달되었을때는 `공변성`의 동작이 반대로되어, **서브 타입**이 상위 타입을 할당할때만 오류가 발생하지 않게됩니다. ## 3. 이변성 📒 + `이변성`은 **함수의 매개변수를 다루는 과정**에서 `공변성`과 `반공변성`을 모두가지는 특성이며, 상위 타입과 서브 타입간의 타입규칙이 이루어지지 않고 오류도 발생하지 않는 특징입니다. ```typescript @@ -60,11 +64,11 @@ type BaseFunction = (params: T) => void; let myFunction: BaseFunction = (param) => { // do something -} +}; let yourFunction: BaseFunction = (param) => { // do something -} +}; myFunction = yourFunction; // 성공 yourFunction = myFunction; // 성공 @@ -74,30 +78,33 @@ yourFunction = myFunction; // 성공 그런데 별도의 설정을 건드리지 않으면 `공변성`과 `반공변성` 규칙이 여전히 존재하고, `이변성`은 이루어지지 않는데요. 이를 어떻게 설정하면 `이변성`의 특징을 사용할 수 있을까요? ### 3-1. strictFunctionType 🖥️ + 정답은 바로! `tsconfig.json` 파일에 작성된 `strictFunctionType` 설정을 건드리면 됩니다. `strictFunctionType` 속성은 기본적으로 `true`로 지정되는데, 이는 **함수의 매개변수가 `반공변적`으로 동작**한다는 의미입니다. 그러나 `false`로 지정하게 되면, **함수의 매개변수는 앞서 말씀드린 특징인 `이변적`으로 동작**하게 됩니다. ```json { // ... 생략 - "strictFunctionType": false, // 이변성 적용 + "strictFunctionType": false // 이변성 적용 } ``` + ```typescript type BaseFunction = (params: T) => void; let myFunction: BaseFunction = (param) => { // do something -} +}; let yourFunction: BaseFunction = (param) => { // do something -} +}; myFunction = yourFunction; // 성공 yourFunction = myFunction; // 성공 ``` ## 4. 마치며 📌 + 오늘은 타입스크립트의 타입 할당관계 규칙인 `공변성`, `반공변성` 그리고 추가적으로 `이변성`에 대해서 알아보았는데요, 이름은 굉장히 생소하게 다가왔던 이 규칙들에 대해서 많은분들이 알게되는데 도움이 되었으면 좋겠습니다. 😃
-이상으로 글을 마치도록 하겠습니다. 읽어주셔서 감사합니다! \ No newline at end of file +이상으로 글을 마치도록 하겠습니다. 읽어주셔서 감사합니다! diff --git a/posts/typescript-mapped-type.mdx b/posts/TypeScript/typescript-mapped-type.mdx similarity index 98% rename from posts/typescript-mapped-type.mdx rename to posts/TypeScript/typescript-mapped-type.mdx index 57c0f98..793fc55 100644 --- a/posts/typescript-mapped-type.mdx +++ b/posts/TypeScript/typescript-mapped-type.mdx @@ -1,9 +1,8 @@ --- title: 타입스크립트 맵드 타입(Mapped Type) 알아보기 description: 타입스크립트의 타입 시스템 문법, 맵드 타입에 대해서 알아보겠습니다. -category: TypeScript createdAt: 2023-11-16 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/e8fb5884-f1a6-4d58-b73c-df9238327ea1 +thumbnail: /images/posts/typescript/typescript-mapped-type/thumbnail.png --- 안녕하세요. 오늘은 타입스크립트의 타입 시스템 문법중 하나인 `Mapped Type(맵드 타입)`에 대해서 알아보겠습니다. diff --git a/posts/typescript-satisfies.mdx b/posts/TypeScript/typescript-satisfies.mdx similarity index 82% rename from posts/typescript-satisfies.mdx rename to posts/TypeScript/typescript-satisfies.mdx index 3b4c506..91beb8e 100644 --- a/posts/typescript-satisfies.mdx +++ b/posts/TypeScript/typescript-satisfies.mdx @@ -1,20 +1,20 @@ --- title: 타입스크립트의 satisfies 키워드를 알아보자 description: 타입스크립트의 satisfies 키워드를 통해서 더 간편한 타입추론하기 -category: TypeScript createdAt: 2023-03-13 -thumbnail: https://user-images.githubusercontent.com/50941453/224694264-c3a4e691-0173-41e9-93c6-4bb99b25e978.png +thumbnail: /images/posts/typescript/typescript-satisfies/thumbnail.png --- -안녕하세요! 오늘은 타입스크립트 4.9 버전에서 추가된 *satisfies* 키워드에 대해서 알아보도록 하겠습니다. 😀 +안녕하세요! 오늘은 타입스크립트 4.9 버전에서 추가된 _satisfies_ 키워드에 대해서 알아보도록 하겠습니다. 😀 ## 1. satisfies를 사용하기 전 + 일반적으로 객체의 값이 `유니온타입` 형태이고, `타입이 지정된 객체`가 있다고 가정할때 아래처럼 사용한다. ```tsx type Colors = 'red' | 'blue' | 'yellow'; -type Color = Record +type Color = Record; const color: Color = { red: '100, 100, 100', @@ -22,7 +22,7 @@ const color: Color = { yellow: ['150', '150', '150'], }; -color.red.charAt(5) // charAt 메소드 사용 시도, 실패 +color.red.charAt(5); // charAt 메소드 사용 시도, 실패 ``` 이 상태에서, `color.red`의 값에 `charAt` 메소드를 적용시키려고 한다. 언뜻 보기에는 문제가 없는것 같으나, 실제로는 문제가 생긴다. @@ -34,6 +34,7 @@ color.red.charAt(5) // charAt 메소드 사용 시도, 실패
`satisfies` 키워드가 나오기 전에는 `as` 키워드를 통해서 문제를 해결할 수 있었다. + ```typescript const color: Color = { red: '100, 100, 100', @@ -41,26 +42,25 @@ const color: Color = { yellow: ['150', '150', '150'], }; -(color.red as string).charAt(5) // charAt 메소드 사용 시도, 성공 +(color.red as string).charAt(5); // charAt 메소드 사용 시도, 성공 ``` 그러나 사용과정에서 위와같은 타입추론의 과정없이 선언 단계에서 이를 해결할 수 있다면 어떨까? ## 2. satisfies가 나온 후 -타입스크립트 4.9 버전에 추가된 `satisfies` 키워드를 사용하면 위와같은 문제를 선언 단계에서 해결할 수 있다. +타입스크립트 4.9 버전에 추가된 `satisfies` 키워드를 사용하면 위와같은 문제를 선언 단계에서 해결할 수 있다. ```tsx type Colors = 'red' | 'blue' | 'yellow'; -type Color = Record +type Color = Record; const color = { red: '100, 100, 100', blue: ['125', '125', '125'], yellow: ['150', '150', '150'], } satisfies Color; - ``` 이렇게 해주면 `color` 객체의 각 프로퍼티 값들이 어느 타입에 해당하는지 명시를 해준다. @@ -68,6 +68,9 @@ const color = { > 위의 예제에서 satisfies 연산자는 value 타입이 유니온타입일때, 각 프로퍼티의 타입들이 어느 유니온 타입에 해당되는지를 검사하여 호환되는 타입을 연결해준다. ## 3. 마치며 ⏰ + 몇달전에 타입스크립트의 `satisfies` 키워드에 대해서 정리를 해두고, 재복습할겸 블로그에 다시 올려보니 괜찮은거 같다. 예전에 정리해둔 공부 문서에서 블로그에 올릴만한 내용들이 있다면 가끔씩 올려야겠다. +
-혹시나 글에서 잘못된 부분이 있다면, 피드백 주시면 감사드리겠습니다! 😀 이상으로 글을 마치도록 하겠습니다. 감사합니다. \ No newline at end of file +혹시나 글에서 잘못된 부분이 있다면, 피드백 주시면 감사드리겠습니다! 😀 이상으로 글을 +마치도록 하겠습니다. 감사합니다. diff --git a/posts/typescript-type-compatibility.mdx b/posts/TypeScript/typescript-type-compatibility.mdx similarity index 87% rename from posts/typescript-type-compatibility.mdx rename to posts/TypeScript/typescript-type-compatibility.mdx index db1491d..716d02d 100644 --- a/posts/typescript-type-compatibility.mdx +++ b/posts/TypeScript/typescript-type-compatibility.mdx @@ -1,18 +1,21 @@ --- title: 구조적 서브타이핑과 타입 호환성 description: 타입스크립트의 구조적 서브타이핑과 타입 호환성에 대해서 알아봅시다. -category: TypeScript createdAt: 2023-05-31 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/76084615-40ef-4ff7-9c45-6a922dc554a6 +thumbnail: /images/posts/typescript/typescript-type-compatibility/thumbnail.png --- + 안녕하세요! 오늘은 타입스크립트에서 **구조적 서브타이핑**이란 무엇이며, 이에 따른 **타입 호환성**에 대해서 알아보도록 하겠습니다. > 본 글은 아래 링크의 글을 읽고나서 참고하여 정리한 글입니다. -[https://toss.tech/article/typescript-type-compatibility](https://toss.tech/article/typescript-type-compatibility) +> [https://toss.tech/article/typescript-type-compatibility](https://toss.tech/article/typescript-type-compatibility) ## 1. 구조적 서브타이핑이란? 📌 + 타입스크립트에서 객체 타입의 **상속관계가 명시되어있지 않아도 객체의 프로퍼티를 기반으로 동일하다면** 사용처에서 타입호환이 가능한 타입스크립트의 특징이다. +
+ 구조적 서브타이핑과 반대되는 개념은 **명목적 서브타이핑**이다. (객체 타입의 상속관계를 명시) ```tsx @@ -71,10 +74,15 @@ printFoodName(food2); ``` 구조적 서브타이핑으로 이루어진 food2 객체에 useFork라는 boolean 변수가 추가되었는데, 이를 인자로 넘기면 어떻게될까? +
-정답은 아무일도 생기지 않는다. 상속관계가 명시되지 않아도, 객체의 프로퍼티 체크 과정에서 통과되었기 때문이다. 구조적 서브타이핑은 `만약 어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽥거리는 소리를 낸다면 나는 그 새를 오리라고 부를 것이다.` 라는 의미에서 **덕 타이핑 (duck typing)** 이라고도 한다. + +정답은 아무일도 생기지 않는다. 상속관계가 명시되지 않아도, 객체의 프로퍼티 체크 과정에서 통과되었기 때문이다. + +구조적 서브타이핑은 `만약 어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽥거리는 소리를 낸다면 나는 그 새를 오리라고 부를 것이다.` 라는 의미에서 **덕 타이핑(duck typing)** 이라고도 한다. ## 2. 타입호환 예외 조건: 신선도(Freshness) 🦄 + 위의 코드 예제와 정말 비슷한 코드가 아래에 있다. ```tsx @@ -98,11 +106,17 @@ printFoodName({ ``` 그러나 위의 코드는 타입호환 검사 과정에서 오류가 발생하게된다. 기존의 코드와 다른점은 **변수에 객체 리터럴에 할당하였느냐, 하지 않았느냐로 볼 수 있다.** 정말 미묘한 차이인거 같은데, 왜 문제가 되는걸까? +
+ TypeScript는 구조적 서브타이핑에 기반한 타입 호환의 예외 조건과 관련하여 [신선도(Freshness)](https://radlohead.gitbook.io/typescript-deep-dive/type-system/freshness)라는 개념을 제공한다. +
-모든 object literal은 초기에 `fresh` 하다고 간주되며, 타입 단언(type assertion) 을 하거나, 타입 추론에 의해 object literal의 타입이 확장되면 `freshness`가 사라지게 된다. + +모든 object literal은 초기에 `fresh` 하다고 간주되며, 타입 단언(type assertion)을 하거나, 타입 추론에 의해 object literal의 타입이 확장되면 `freshness`가 사라지게 된다. +
+ 특정한 변수에 object literal을 할당하는 경우 이 2가지 중 한가지가 발생하게 되므로, `freshness`가 사라지게 되며, 함수에 인자로 object literal을 바로 전달하는 경우에는 `fresh`한 상태로 전달된다. ```tsx @@ -138,9 +152,13 @@ printFoodName({ ``` fresh object를 함수에 인자로 전달한 경우, 이는 특정한 변수에 할당되지 않았으므로 **해당 함수에서만 사용되고 다른 곳에서 사용되지 않는다는 특징**이 존재한다. + 따라서 이 경우 유연함에 대한 이점보다는 부작용을 발생시킬 가능성이 높으므로 굳이 구조적 서브타이핑을 지원해야할 이유가 없다. ## 3. 마치며 😃 + 오늘은 타입스크립트의 **구조적/명목적 서브타이핑**이란 무엇인지, 이에 따른 **타입 호환성 시스템**이 어떻게 동작하는지 알아보았는데요. 평소에 개발하면서 위와 비슷한 객체 문제들을 겪었던 경험이 있었는데, 다음부터 이를 참고한다면 객체를 다루는데 어려움이 줄어들 수 있을거 같습니다! 👍 +
-이상으로 마치겠습니다. 긴 글 읽어주셔서 감사합니다! \ No newline at end of file + +이상으로 마치겠습니다. 긴 글 읽어주셔서 감사합니다! diff --git a/posts/windows-kill-process.mdx b/posts/Windows/windows-kill-process.mdx similarity index 78% rename from posts/windows-kill-process.mdx rename to posts/Windows/windows-kill-process.mdx index 4a4df13..d3202cd 100644 --- a/posts/windows-kill-process.mdx +++ b/posts/Windows/windows-kill-process.mdx @@ -2,8 +2,7 @@ title: Windows 프로세스 죽이기 createdAt: 2023-02-27 description: 안녕하세요! 오늘은 간단하게 윈도우에서 실행중인 프로세스를 찾아서 포트 죽이기를 주제로 글을 작성해보도록 하겠습니다. -category: Windows -thumbnail: https://velog.velcdn.com/images/yiyb0603/post/075df0ff-7a7a-4cec-81b4-dba61f6ddd4c/1_1.jpg +thumbnail: /images/posts/windows/windows-kill-process/thumbnail.jpg --- 안녕하세요! 오늘은 간단하게 **윈도우에서 실행중인 프로세스를 찾아서 포트 죽이기**를 주제로 글을 작성해보도록 하겠습니다. 제가 자주 해결하는 방법이기도 해서 메모겸으로 글을 작성해보려고 합니다 😁 @@ -12,7 +11,7 @@ thumbnail: https://velog.velcdn.com/images/yiyb0603/post/075df0ff-7a7a-4cec-81b4 **보통 백엔드를 하시는분들 이라면 아래와 같은 포트 오류를 한번쯤 경험 해보셨을 것입니다.** -![](https://images.velog.io/images/yiyb0603/post/68317760-b0ea-40fa-b313-b1b1f590e1cf/image.png) +![이미 사용중인 서버 포트](/images/posts/windows/windows-kill-process/already-use-port.png) 서버를 **8080 포트 번호**로 실행을 했을때, 이미 8080 포트에서 돌아가고 있는 프로세스가 존재하고 있기 때문이죠. **이때 해결방법은 총 두가지가 있습니다.** @@ -22,23 +21,23 @@ thumbnail: https://velog.velcdn.com/images/yiyb0603/post/075df0ff-7a7a-4cec-81b4 ## 2. 해결법 🎃 -![](https://images.velog.io/images/yiyb0603/post/1df14e57-9173-412b-af74-6ffce7c4a6e2/image.png) +![cmd 열기](/images/posts/windows/windows-kill-process/open-terminal.png) 화면에서 **Windows + R**키를 누른다음 **cmd**를 입력하여 콘솔창을 열도록 합니다. 그 다음으로 **netstat -a -o**라는 문장을 **cmd**에 입력합니다. -![](https://images.velog.io/images/yiyb0603/post/854449e3-0f22-4396-b193-ed423fda0362/image.png) +![실행중인 프로세스 목록](/images/posts/windows/windows-kill-process/process-list.png) 위와 같이 현재 실행중인 프로세스 목록들이 쭉 뜨게됩니다. 그중에서 **Local Address**의 주소중에서 포트가 **8080**으로 실행중인 프로세스의 **PID**를 찾습니다. 저의 PID는 14236입니다. PID를 찾은 다음, 아래의 문장을 입력합니다. -``` +```cmd taskkill /f /pid 14236 ``` -![](https://images.velog.io/images/yiyb0603/post/6aa9be60-2d1e-437d-b1ff-54cc841611ff/image.png) +![프로세스 죽이기 성공](/images/posts/windows/windows-kill-process/kill-success.png) Success가 출력되면서 성공적으로 해당 PID의 프로세스가 죽었음을 알 수 있습니다. 이러고나서 서버를 다시 실행하면 성공적으로 실행이 됩니다. 😁😁 -이상으로 글을 마치도록 하겠습니다! +이상으로 글을 마치도록 하겠습니다! 글 읽어주셔서 감사합니다. diff --git a/posts/npm-install-and-ci.mdx b/posts/npm/npm-install-and-ci.mdx similarity index 97% rename from posts/npm-install-and-ci.mdx rename to posts/npm/npm-install-and-ci.mdx index 62583df..089f83a 100644 --- a/posts/npm-install-and-ci.mdx +++ b/posts/npm/npm-install-and-ci.mdx @@ -1,9 +1,8 @@ --- title: npm install과 npm ci 명령어 알아보기 description: npm 패키지를 설치할때 대표적으로 사용되는 두 명령어를 알아보겠습니다. -category: npm createdAt: 2023-11-08 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/81ec58db-916b-429a-b38f-50e55367e7aa +thumbnail: /images/posts/npm/npm-install-and-ci/thumbnail.png --- 안녕하세요! 오늘은 npm 패키지를 설치할 때 많이 사용되는 두가지 명령어, `npm install`과 `npm ci`란 무엇이고, 차이점은 무엇인지 알아보겠습니다. diff --git a/posts/npm-tilde-carrot.mdx b/posts/npm/npm-tilde-carrot.mdx similarity index 90% rename from posts/npm-tilde-carrot.mdx rename to posts/npm/npm-tilde-carrot.mdx index 3970a81..f99bb29 100644 --- a/posts/npm-tilde-carrot.mdx +++ b/posts/npm/npm-tilde-carrot.mdx @@ -1,9 +1,8 @@ --- title: npm 패키지 버전관리 틸드(~)와 캐럿(^) description: npm 패키지 버전관리시에 사용되는 틸드(~)와 캐럿(^) 방식에 대해서 알아보겠습니다. -category: npm createdAt: 2023-05-10 -thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/4c982ca9-7c9a-4a2d-abb9-f203db0c7db2 +thumbnail: /images/posts/npm/npm-tilde-carrot/thumbnail.png --- 안녕하세요! 오늘은 npm 패키지 버전방식중 틸드(~)와 **캐럿(^)** 방식에 대해서 알아보도록 하겠습니다. @@ -12,7 +11,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/4c982ca9-7c9a-4 평소에 React, Next.js 등으로 코딩을 할때 자주보는 **package.json** 파일에는 틸드(~)와 캐럿(^) 방식이 많이 사용된것을 볼 수 있는데요, package.json 파일에서 라이브러리 버전들을 관리하면서 이 두 방식에 대해서 궁금증을 가지게 되었고, 알아보게 되었습니다. -![package.json](https://github.com/mantinedev/postcss-preset-mantine/assets/50941453/c3b16389-b06e-497f-b118-28da2e1da7da) +![package.json](/images/posts/npm/npm-tilde-carrot/package-json.png) > package.json 파일에 명시된 틸드, 캐럿 버전 방식 @@ -20,7 +19,7 @@ thumbnail: https://github.com/yiyb0603/yiyb-blog/assets/50941453/4c982ca9-7c9a-4 일반적으로 소프트웨어 배포 후 그에 따른 버저닝을 하게되는데요, 버전 자리마다 각각 **메이저**, **마이너**, **패치**로 통칭됩니다. -![소프트웨어 버저닝](https://github.com/mantinedev/postcss-preset-mantine/assets/50941453/b30c24d3-95e5-41cb-8f4a-a681a19e3220) +![소프트웨어 버저닝](/images/posts/npm/npm-tilde-carrot/software-versioning.png) > **메이저**: 하위호환이 되지 않을정도의 변화 > **마이너**: 하위호환이 되는 범위내에서 **새로운 기능 추가** 버전 diff --git a/public/images/posts/develop/difference-kakao-sdk-and-url/kakao-login-guide-document.png b/public/images/posts/develop/difference-kakao-sdk-and-url/kakao-login-guide-document.png new file mode 100644 index 0000000..d82a3fc Binary files /dev/null and b/public/images/posts/develop/difference-kakao-sdk-and-url/kakao-login-guide-document.png differ diff --git a/public/images/posts/develop/difference-kakao-sdk-and-url/kakao-sdk-execute-login.png b/public/images/posts/develop/difference-kakao-sdk-and-url/kakao-sdk-execute-login.png new file mode 100644 index 0000000..d5ea84f Binary files /dev/null and b/public/images/posts/develop/difference-kakao-sdk-and-url/kakao-sdk-execute-login.png differ diff --git a/public/images/posts/develop/difference-kakao-sdk-and-url/rest-api-execute-login.png b/public/images/posts/develop/difference-kakao-sdk-and-url/rest-api-execute-login.png new file mode 100644 index 0000000..a4993c2 Binary files /dev/null and b/public/images/posts/develop/difference-kakao-sdk-and-url/rest-api-execute-login.png differ diff --git a/public/images/posts/develop/difference-kakao-sdk-and-url/thumbnail.png b/public/images/posts/develop/difference-kakao-sdk-and-url/thumbnail.png new file mode 100644 index 0000000..bfc0d86 Binary files /dev/null and b/public/images/posts/develop/difference-kakao-sdk-and-url/thumbnail.png differ diff --git a/public/images/posts/develop/forward-proxy-reverse-proxy/forward-proxy.png b/public/images/posts/develop/forward-proxy-reverse-proxy/forward-proxy.png new file mode 100644 index 0000000..be9e0ed Binary files /dev/null and b/public/images/posts/develop/forward-proxy-reverse-proxy/forward-proxy.png differ diff --git a/public/images/posts/develop/forward-proxy-reverse-proxy/proxy-server.png b/public/images/posts/develop/forward-proxy-reverse-proxy/proxy-server.png new file mode 100644 index 0000000..b5ae88e Binary files /dev/null and b/public/images/posts/develop/forward-proxy-reverse-proxy/proxy-server.png differ diff --git a/public/images/posts/develop/forward-proxy-reverse-proxy/reverse-proxy.png b/public/images/posts/develop/forward-proxy-reverse-proxy/reverse-proxy.png new file mode 100644 index 0000000..e64fbed Binary files /dev/null and b/public/images/posts/develop/forward-proxy-reverse-proxy/reverse-proxy.png differ diff --git a/public/images/posts/develop/forward-proxy-reverse-proxy/thumbnail.png b/public/images/posts/develop/forward-proxy-reverse-proxy/thumbnail.png new file mode 100644 index 0000000..366c5c6 Binary files /dev/null and b/public/images/posts/develop/forward-proxy-reverse-proxy/thumbnail.png differ diff --git a/public/images/posts/develop/giscus-comment-usage/check-discussions.png b/public/images/posts/develop/giscus-comment-usage/check-discussions.png new file mode 100644 index 0000000..57dc127 Binary files /dev/null and b/public/images/posts/develop/giscus-comment-usage/check-discussions.png differ diff --git a/public/images/posts/develop/giscus-comment-usage/create-new-category.png b/public/images/posts/develop/giscus-comment-usage/create-new-category.png new file mode 100644 index 0000000..3408991 Binary files /dev/null and b/public/images/posts/develop/giscus-comment-usage/create-new-category.png differ diff --git a/public/images/posts/develop/giscus-comment-usage/discussion-category.png b/public/images/posts/develop/giscus-comment-usage/discussion-category.png new file mode 100644 index 0000000..a0c598c Binary files /dev/null and b/public/images/posts/develop/giscus-comment-usage/discussion-category.png differ diff --git a/public/images/posts/develop/giscus-comment-usage/discussion-connect.png b/public/images/posts/develop/giscus-comment-usage/discussion-connect.png new file mode 100644 index 0000000..f015cee Binary files /dev/null and b/public/images/posts/develop/giscus-comment-usage/discussion-connect.png differ diff --git a/public/images/posts/develop/giscus-comment-usage/giscus-repository.png b/public/images/posts/develop/giscus-comment-usage/giscus-repository.png new file mode 100644 index 0000000..d4afaab Binary files /dev/null and b/public/images/posts/develop/giscus-comment-usage/giscus-repository.png differ diff --git a/public/images/posts/develop/giscus-comment-usage/giscus-script.png b/public/images/posts/develop/giscus-comment-usage/giscus-script.png new file mode 100644 index 0000000..458fa7e Binary files /dev/null and b/public/images/posts/develop/giscus-comment-usage/giscus-script.png differ diff --git a/public/images/posts/develop/giscus-comment-usage/giscus-settings.png b/public/images/posts/develop/giscus-comment-usage/giscus-settings.png new file mode 100644 index 0000000..f82225e Binary files /dev/null and b/public/images/posts/develop/giscus-comment-usage/giscus-settings.png differ diff --git a/public/images/posts/develop/giscus-comment-usage/issue-to-discussion.png b/public/images/posts/develop/giscus-comment-usage/issue-to-discussion.png new file mode 100644 index 0000000..245f55d Binary files /dev/null and b/public/images/posts/develop/giscus-comment-usage/issue-to-discussion.png differ diff --git a/public/images/posts/develop/giscus-comment-usage/thumbnail.png b/public/images/posts/develop/giscus-comment-usage/thumbnail.png new file mode 100644 index 0000000..23d88f3 Binary files /dev/null and b/public/images/posts/develop/giscus-comment-usage/thumbnail.png differ diff --git a/public/images/posts/develop/github-actions-reusable-workflow/create-repository.png b/public/images/posts/develop/github-actions-reusable-workflow/create-repository.png new file mode 100644 index 0000000..fe1f8d7 Binary files /dev/null and b/public/images/posts/develop/github-actions-reusable-workflow/create-repository.png differ diff --git a/public/images/posts/develop/github-actions-reusable-workflow/create-workflow-file.png b/public/images/posts/develop/github-actions-reusable-workflow/create-workflow-file.png new file mode 100644 index 0000000..e854a40 Binary files /dev/null and b/public/images/posts/develop/github-actions-reusable-workflow/create-workflow-file.png differ diff --git a/public/images/posts/develop/github-actions-reusable-workflow/thumbnail.png b/public/images/posts/develop/github-actions-reusable-workflow/thumbnail.png new file mode 100644 index 0000000..8d099c2 Binary files /dev/null and b/public/images/posts/develop/github-actions-reusable-workflow/thumbnail.png differ diff --git a/public/images/posts/develop/github-actions-reusable-workflow/vscode-extension.png b/public/images/posts/develop/github-actions-reusable-workflow/vscode-extension.png new file mode 100644 index 0000000..a948517 Binary files /dev/null and b/public/images/posts/develop/github-actions-reusable-workflow/vscode-extension.png differ diff --git a/public/images/posts/develop/github-actions-reusable-workflow/workflow-access.png b/public/images/posts/develop/github-actions-reusable-workflow/workflow-access.png new file mode 100644 index 0000000..be5e5c7 Binary files /dev/null and b/public/images/posts/develop/github-actions-reusable-workflow/workflow-access.png differ diff --git a/public/images/posts/develop/github-actions-reusable-workflow/workflow-result.png b/public/images/posts/develop/github-actions-reusable-workflow/workflow-result.png new file mode 100644 index 0000000..e80c6f1 Binary files /dev/null and b/public/images/posts/develop/github-actions-reusable-workflow/workflow-result.png differ diff --git a/public/images/posts/develop/msw-usage/from-service-worker.png b/public/images/posts/develop/msw-usage/from-service-worker.png new file mode 100644 index 0000000..597154d Binary files /dev/null and b/public/images/posts/develop/msw-usage/from-service-worker.png differ diff --git a/public/images/posts/develop/msw-usage/msw-get-response.png b/public/images/posts/develop/msw-usage/msw-get-response.png new file mode 100644 index 0000000..2776c2d Binary files /dev/null and b/public/images/posts/develop/msw-usage/msw-get-response.png differ diff --git a/public/images/posts/develop/msw-usage/msw-post-response.png b/public/images/posts/develop/msw-usage/msw-post-response.png new file mode 100644 index 0000000..ccb95f1 Binary files /dev/null and b/public/images/posts/develop/msw-usage/msw-post-response.png differ diff --git a/public/images/posts/develop/msw-usage/msw.png b/public/images/posts/develop/msw-usage/msw.png new file mode 100644 index 0000000..710bc73 Binary files /dev/null and b/public/images/posts/develop/msw-usage/msw.png differ diff --git a/public/images/posts/develop/msw-usage/thumbnail.png b/public/images/posts/develop/msw-usage/thumbnail.png new file mode 100644 index 0000000..e79a2e1 Binary files /dev/null and b/public/images/posts/develop/msw-usage/thumbnail.png differ diff --git a/public/images/posts/develop/useful-http-cache/browser-server-relation.png b/public/images/posts/develop/useful-http-cache/browser-server-relation.png new file mode 100644 index 0000000..63f408d Binary files /dev/null and b/public/images/posts/develop/useful-http-cache/browser-server-relation.png differ diff --git a/public/images/posts/develop/useful-http-cache/cdn-invalidation.png b/public/images/posts/develop/useful-http-cache/cdn-invalidation.png new file mode 100644 index 0000000..5beb790 Binary files /dev/null and b/public/images/posts/develop/useful-http-cache/cdn-invalidation.png differ diff --git a/public/images/posts/develop/useful-http-cache/thumbnail.png b/public/images/posts/develop/useful-http-cache/thumbnail.png new file mode 100644 index 0000000..d90ff65 Binary files /dev/null and b/public/images/posts/develop/useful-http-cache/thumbnail.png differ diff --git a/public/images/posts/develop/useful-http-cache/unique-files.png b/public/images/posts/develop/useful-http-cache/unique-files.png new file mode 100644 index 0000000..6f87858 Binary files /dev/null and b/public/images/posts/develop/useful-http-cache/unique-files.png differ diff --git a/public/images/posts/develop/utm-parameters/thumbnail.png b/public/images/posts/develop/utm-parameters/thumbnail.png new file mode 100644 index 0000000..5f33658 Binary files /dev/null and b/public/images/posts/develop/utm-parameters/thumbnail.png differ diff --git a/public/images/posts/html/noopener-and-noreferrer/tab-nabbing.png b/public/images/posts/html/noopener-and-noreferrer/tab-nabbing.png new file mode 100644 index 0000000..2e4ab9a Binary files /dev/null and b/public/images/posts/html/noopener-and-noreferrer/tab-nabbing.png differ diff --git a/public/images/posts/html/noopener-and-noreferrer/thumbnail.png b/public/images/posts/html/noopener-and-noreferrer/thumbnail.png new file mode 100644 index 0000000..8f23b96 Binary files /dev/null and b/public/images/posts/html/noopener-and-noreferrer/thumbnail.png differ diff --git a/public/images/posts/javascript/js-async-and-defer/async.png b/public/images/posts/javascript/js-async-and-defer/async.png new file mode 100644 index 0000000..d387f09 Binary files /dev/null and b/public/images/posts/javascript/js-async-and-defer/async.png differ diff --git a/public/images/posts/javascript/js-async-and-defer/defer.png b/public/images/posts/javascript/js-async-and-defer/defer.png new file mode 100644 index 0000000..dfcf1d5 Binary files /dev/null and b/public/images/posts/javascript/js-async-and-defer/defer.png differ diff --git a/public/images/posts/javascript/js-async-and-defer/original.png b/public/images/posts/javascript/js-async-and-defer/original.png new file mode 100644 index 0000000..abcb34b Binary files /dev/null and b/public/images/posts/javascript/js-async-and-defer/original.png differ diff --git a/public/images/posts/javascript/js-async-and-defer/thumbnail.png b/public/images/posts/javascript/js-async-and-defer/thumbnail.png new file mode 100644 index 0000000..2f8e4fc Binary files /dev/null and b/public/images/posts/javascript/js-async-and-defer/thumbnail.png differ diff --git a/public/images/posts/life/2023-retrospect/gbsw-lecture.jpeg b/public/images/posts/life/2023-retrospect/gbsw-lecture.jpeg new file mode 100644 index 0000000..bfdff2c Binary files /dev/null and b/public/images/posts/life/2023-retrospect/gbsw-lecture.jpeg differ diff --git a/public/images/posts/life/2023-retrospect/industrial-technical-personnel.png b/public/images/posts/life/2023-retrospect/industrial-technical-personnel.png new file mode 100644 index 0000000..9c3fb76 Binary files /dev/null and b/public/images/posts/life/2023-retrospect/industrial-technical-personnel.png differ diff --git a/public/images/posts/life/2023-retrospect/nextjs-contentlayer-google-search.png b/public/images/posts/life/2023-retrospect/nextjs-contentlayer-google-search.png new file mode 100644 index 0000000..1d221b6 Binary files /dev/null and b/public/images/posts/life/2023-retrospect/nextjs-contentlayer-google-search.png differ diff --git a/public/images/posts/life/2023-retrospect/react-query-google-search.png b/public/images/posts/life/2023-retrospect/react-query-google-search.png new file mode 100644 index 0000000..f975d68 Binary files /dev/null and b/public/images/posts/life/2023-retrospect/react-query-google-search.png differ diff --git a/public/images/posts/life/2023-retrospect/search-console.png b/public/images/posts/life/2023-retrospect/search-console.png new file mode 100644 index 0000000..0c99ffa Binary files /dev/null and b/public/images/posts/life/2023-retrospect/search-console.png differ diff --git a/public/images/posts/life/2023-retrospect/thumbnail.png b/public/images/posts/life/2023-retrospect/thumbnail.png new file mode 100644 index 0000000..e04231b Binary files /dev/null and b/public/images/posts/life/2023-retrospect/thumbnail.png differ diff --git a/public/images/posts/life/2023-retrospect/toss-failed.png b/public/images/posts/life/2023-retrospect/toss-failed.png new file mode 100644 index 0000000..befc7f0 Binary files /dev/null and b/public/images/posts/life/2023-retrospect/toss-failed.png differ diff --git a/public/images/posts/life/2023-retrospect/toss-homework-pass.png b/public/images/posts/life/2023-retrospect/toss-homework-pass.png new file mode 100644 index 0000000..02c4ce5 Binary files /dev/null and b/public/images/posts/life/2023-retrospect/toss-homework-pass.png differ diff --git a/public/images/posts/life/2023-retrospect/toss-portfolio-pass.png b/public/images/posts/life/2023-retrospect/toss-portfolio-pass.png new file mode 100644 index 0000000..ac96657 Binary files /dev/null and b/public/images/posts/life/2023-retrospect/toss-portfolio-pass.png differ diff --git a/public/images/posts/life/2023-retrospect/toss-portfolio-submit.png b/public/images/posts/life/2023-retrospect/toss-portfolio-submit.png new file mode 100644 index 0000000..39913bc Binary files /dev/null and b/public/images/posts/life/2023-retrospect/toss-portfolio-submit.png differ diff --git a/public/images/posts/life/2023-retrospect/toss-technical-interview-pass.png b/public/images/posts/life/2023-retrospect/toss-technical-interview-pass.png new file mode 100644 index 0000000..c7fd1e9 Binary files /dev/null and b/public/images/posts/life/2023-retrospect/toss-technical-interview-pass.png differ diff --git a/public/images/posts/life/2023-retrospect/vercel.png b/public/images/posts/life/2023-retrospect/vercel.png new file mode 100644 index 0000000..15bc118 Binary files /dev/null and b/public/images/posts/life/2023-retrospect/vercel.png differ diff --git a/public/images/posts/life/2023-retrospect/yiyb-blog.png b/public/images/posts/life/2023-retrospect/yiyb-blog.png new file mode 100644 index 0000000..1995ee6 Binary files /dev/null and b/public/images/posts/life/2023-retrospect/yiyb-blog.png differ diff --git a/public/images/posts/life/basic-military-training/daegu-50-division.png b/public/images/posts/life/basic-military-training/daegu-50-division.png new file mode 100644 index 0000000..cc69845 Binary files /dev/null and b/public/images/posts/life/basic-military-training/daegu-50-division.png differ diff --git a/public/images/posts/life/basic-military-training/thumbnail.png b/public/images/posts/life/basic-military-training/thumbnail.png new file mode 100644 index 0000000..c358989 Binary files /dev/null and b/public/images/posts/life/basic-military-training/thumbnail.png differ diff --git a/public/images/posts/life/feels-of-live-alone-two-months/home.jpeg b/public/images/posts/life/feels-of-live-alone-two-months/home.jpeg new file mode 100644 index 0000000..c8dc8bb Binary files /dev/null and b/public/images/posts/life/feels-of-live-alone-two-months/home.jpeg differ diff --git a/public/images/posts/life/feels-of-live-alone-two-months/think-money-spend.png b/public/images/posts/life/feels-of-live-alone-two-months/think-money-spend.png new file mode 100644 index 0000000..984198a Binary files /dev/null and b/public/images/posts/life/feels-of-live-alone-two-months/think-money-spend.png differ diff --git a/public/images/posts/life/feels-of-live-alone-two-months/thumbnail.png b/public/images/posts/life/feels-of-live-alone-two-months/thumbnail.png new file mode 100644 index 0000000..26ddd96 Binary files /dev/null and b/public/images/posts/life/feels-of-live-alone-two-months/thumbnail.png differ diff --git a/public/images/posts/life/feels-of-live-alone-two-months/tired.png b/public/images/posts/life/feels-of-live-alone-two-months/tired.png new file mode 100644 index 0000000..0918bef Binary files /dev/null and b/public/images/posts/life/feels-of-live-alone-two-months/tired.png differ diff --git a/public/images/posts/life/gbsw-frontend-lecture/browser-storages.png b/public/images/posts/life/gbsw-frontend-lecture/browser-storages.png new file mode 100644 index 0000000..9048480 Binary files /dev/null and b/public/images/posts/life/gbsw-frontend-lecture/browser-storages.png differ diff --git a/public/images/posts/life/gbsw-frontend-lecture/gbsw-panorama.png b/public/images/posts/life/gbsw-frontend-lecture/gbsw-panorama.png new file mode 100644 index 0000000..84ebfa4 Binary files /dev/null and b/public/images/posts/life/gbsw-frontend-lecture/gbsw-panorama.png differ diff --git a/public/images/posts/life/gbsw-frontend-lecture/js-array-methods.png b/public/images/posts/life/gbsw-frontend-lecture/js-array-methods.png new file mode 100644 index 0000000..01e5186 Binary files /dev/null and b/public/images/posts/life/gbsw-frontend-lecture/js-array-methods.png differ diff --git a/public/images/posts/life/gbsw-frontend-lecture/lecture.jpeg b/public/images/posts/life/gbsw-frontend-lecture/lecture.jpeg new file mode 100644 index 0000000..674451c Binary files /dev/null and b/public/images/posts/life/gbsw-frontend-lecture/lecture.jpeg differ diff --git a/public/images/posts/life/gbsw-frontend-lecture/react-hooks.png b/public/images/posts/life/gbsw-frontend-lecture/react-hooks.png new file mode 100644 index 0000000..fcc950d Binary files /dev/null and b/public/images/posts/life/gbsw-frontend-lecture/react-hooks.png differ diff --git a/public/images/posts/life/gbsw-frontend-lecture/react.png b/public/images/posts/life/gbsw-frontend-lecture/react.png new file mode 100644 index 0000000..b48c8f1 Binary files /dev/null and b/public/images/posts/life/gbsw-frontend-lecture/react.png differ diff --git a/public/images/posts/life/gbsw-frontend-lecture/rest-api.png b/public/images/posts/life/gbsw-frontend-lecture/rest-api.png new file mode 100644 index 0000000..fab1e0a Binary files /dev/null and b/public/images/posts/life/gbsw-frontend-lecture/rest-api.png differ diff --git a/public/images/posts/life/gbsw-frontend-lecture/styled-components.png b/public/images/posts/life/gbsw-frontend-lecture/styled-components.png new file mode 100644 index 0000000..e7253ed Binary files /dev/null and b/public/images/posts/life/gbsw-frontend-lecture/styled-components.png differ diff --git a/public/images/posts/life/gbsw-frontend-lecture/thumbnail.png b/public/images/posts/life/gbsw-frontend-lecture/thumbnail.png new file mode 100644 index 0000000..2bcb719 Binary files /dev/null and b/public/images/posts/life/gbsw-frontend-lecture/thumbnail.png differ diff --git a/public/images/posts/life/gbsw-frontend-lecture/todo-list.png b/public/images/posts/life/gbsw-frontend-lecture/todo-list.png new file mode 100644 index 0000000..e4f6f67 Binary files /dev/null and b/public/images/posts/life/gbsw-frontend-lecture/todo-list.png differ diff --git a/public/images/posts/next.js/nextjs-contentlayer-blog/contentlayer-generated.png b/public/images/posts/next.js/nextjs-contentlayer-blog/contentlayer-generated.png new file mode 100644 index 0000000..8ef342d Binary files /dev/null and b/public/images/posts/next.js/nextjs-contentlayer-blog/contentlayer-generated.png differ diff --git a/public/images/posts/next.js/nextjs-contentlayer-blog/despair.png b/public/images/posts/next.js/nextjs-contentlayer-blog/despair.png new file mode 100644 index 0000000..fefab98 Binary files /dev/null and b/public/images/posts/next.js/nextjs-contentlayer-blog/despair.png differ diff --git a/public/images/posts/next.js/nextjs-contentlayer-blog/so-many-errors.png b/public/images/posts/next.js/nextjs-contentlayer-blog/so-many-errors.png new file mode 100644 index 0000000..247b334 Binary files /dev/null and b/public/images/posts/next.js/nextjs-contentlayer-blog/so-many-errors.png differ diff --git a/public/images/posts/next.js/nextjs-contentlayer-blog/thumb.png b/public/images/posts/next.js/nextjs-contentlayer-blog/thumb.png new file mode 100644 index 0000000..b1f082f Binary files /dev/null and b/public/images/posts/next.js/nextjs-contentlayer-blog/thumb.png differ diff --git a/public/images/posts/next.js/nextjs-contentlayer-blog/thumbnail.png b/public/images/posts/next.js/nextjs-contentlayer-blog/thumbnail.png new file mode 100644 index 0000000..96f85e0 Binary files /dev/null and b/public/images/posts/next.js/nextjs-contentlayer-blog/thumbnail.png differ diff --git a/public/images/posts/next.js/nextjs-contentlayer-blog/unicode-error.png b/public/images/posts/next.js/nextjs-contentlayer-blog/unicode-error.png new file mode 100644 index 0000000..84c1a30 Binary files /dev/null and b/public/images/posts/next.js/nextjs-contentlayer-blog/unicode-error.png differ diff --git a/public/images/posts/next.js/nextjs-contentlayer-blog/you-have-a-plan.png b/public/images/posts/next.js/nextjs-contentlayer-blog/you-have-a-plan.png new file mode 100644 index 0000000..7695a53 Binary files /dev/null and b/public/images/posts/next.js/nextjs-contentlayer-blog/you-have-a-plan.png differ diff --git a/public/images/posts/next.js/nextjs-dynamic-sitemap/crawling.png b/public/images/posts/next.js/nextjs-dynamic-sitemap/crawling.png new file mode 100644 index 0000000..39a8073 Binary files /dev/null and b/public/images/posts/next.js/nextjs-dynamic-sitemap/crawling.png differ diff --git a/public/images/posts/next.js/nextjs-dynamic-sitemap/create-project.png b/public/images/posts/next.js/nextjs-dynamic-sitemap/create-project.png new file mode 100644 index 0000000..a875c2e Binary files /dev/null and b/public/images/posts/next.js/nextjs-dynamic-sitemap/create-project.png differ diff --git a/public/images/posts/next.js/nextjs-dynamic-sitemap/create-sitemap-file.png b/public/images/posts/next.js/nextjs-dynamic-sitemap/create-sitemap-file.png new file mode 100644 index 0000000..b646e2d Binary files /dev/null and b/public/images/posts/next.js/nextjs-dynamic-sitemap/create-sitemap-file.png differ diff --git a/public/images/posts/next.js/nextjs-dynamic-sitemap/created-sitemap.png b/public/images/posts/next.js/nextjs-dynamic-sitemap/created-sitemap.png new file mode 100644 index 0000000..c479cdc Binary files /dev/null and b/public/images/posts/next.js/nextjs-dynamic-sitemap/created-sitemap.png differ diff --git a/public/images/posts/next.js/nextjs-dynamic-sitemap/jsonplaceholder-response.png b/public/images/posts/next.js/nextjs-dynamic-sitemap/jsonplaceholder-response.png new file mode 100644 index 0000000..6879c67 Binary files /dev/null and b/public/images/posts/next.js/nextjs-dynamic-sitemap/jsonplaceholder-response.png differ diff --git a/public/images/posts/next.js/nextjs-dynamic-sitemap/second-post.png b/public/images/posts/next.js/nextjs-dynamic-sitemap/second-post.png new file mode 100644 index 0000000..ed6af2e Binary files /dev/null and b/public/images/posts/next.js/nextjs-dynamic-sitemap/second-post.png differ diff --git a/public/images/posts/next.js/nextjs-dynamic-sitemap/sitemap.png b/public/images/posts/next.js/nextjs-dynamic-sitemap/sitemap.png new file mode 100644 index 0000000..f8e0039 Binary files /dev/null and b/public/images/posts/next.js/nextjs-dynamic-sitemap/sitemap.png differ diff --git a/public/images/posts/next.js/nextjs-dynamic-sitemap/ten-post.png b/public/images/posts/next.js/nextjs-dynamic-sitemap/ten-post.png new file mode 100644 index 0000000..f70d96c Binary files /dev/null and b/public/images/posts/next.js/nextjs-dynamic-sitemap/ten-post.png differ diff --git a/public/images/posts/next.js/nextjs-dynamic-sitemap/thumbnail.png b/public/images/posts/next.js/nextjs-dynamic-sitemap/thumbnail.png new file mode 100644 index 0000000..4611abc Binary files /dev/null and b/public/images/posts/next.js/nextjs-dynamic-sitemap/thumbnail.png differ diff --git a/public/images/posts/next.js/nextjs-dynamic-sitemap/velog.png b/public/images/posts/next.js/nextjs-dynamic-sitemap/velog.png new file mode 100644 index 0000000..b2649d8 Binary files /dev/null and b/public/images/posts/next.js/nextjs-dynamic-sitemap/velog.png differ diff --git a/public/images/posts/next.js/nextjs-standalone/standalone-build-result.png b/public/images/posts/next.js/nextjs-standalone/standalone-build-result.png new file mode 100644 index 0000000..bc75ed7 Binary files /dev/null and b/public/images/posts/next.js/nextjs-standalone/standalone-build-result.png differ diff --git a/public/images/posts/next.js/nextjs-standalone/static-resource-failed.png b/public/images/posts/next.js/nextjs-standalone/static-resource-failed.png new file mode 100644 index 0000000..f28d500 Binary files /dev/null and b/public/images/posts/next.js/nextjs-standalone/static-resource-failed.png differ diff --git a/public/images/posts/next.js/nextjs-standalone/static-resource-success.png b/public/images/posts/next.js/nextjs-standalone/static-resource-success.png new file mode 100644 index 0000000..3f0aad5 Binary files /dev/null and b/public/images/posts/next.js/nextjs-standalone/static-resource-success.png differ diff --git a/public/images/posts/next.js/nextjs-standalone/thumbnail.png b/public/images/posts/next.js/nextjs-standalone/thumbnail.png new file mode 100644 index 0000000..51f4e06 Binary files /dev/null and b/public/images/posts/next.js/nextjs-standalone/thumbnail.png differ diff --git a/public/images/posts/npm/npm-install-and-ci/thumbnail.png b/public/images/posts/npm/npm-install-and-ci/thumbnail.png new file mode 100644 index 0000000..33c89f5 Binary files /dev/null and b/public/images/posts/npm/npm-install-and-ci/thumbnail.png differ diff --git a/public/images/posts/npm/npm-tilde-carrot/package-json.png b/public/images/posts/npm/npm-tilde-carrot/package-json.png new file mode 100644 index 0000000..4ecb784 Binary files /dev/null and b/public/images/posts/npm/npm-tilde-carrot/package-json.png differ diff --git a/public/images/posts/npm/npm-tilde-carrot/software-versioning.png b/public/images/posts/npm/npm-tilde-carrot/software-versioning.png new file mode 100644 index 0000000..7e4cf2c Binary files /dev/null and b/public/images/posts/npm/npm-tilde-carrot/software-versioning.png differ diff --git a/public/images/posts/npm/npm-tilde-carrot/thumbnail.png b/public/images/posts/npm/npm-tilde-carrot/thumbnail.png new file mode 100644 index 0000000..440ceb2 Binary files /dev/null and b/public/images/posts/npm/npm-tilde-carrot/thumbnail.png differ diff --git a/public/images/posts/react-query/error-boundary-with-react-query/thumbnail.png b/public/images/posts/react-query/error-boundary-with-react-query/thumbnail.png new file mode 100644 index 0000000..b440785 Binary files /dev/null and b/public/images/posts/react-query/error-boundary-with-react-query/thumbnail.png differ diff --git a/public/images/posts/react-query/react-query-loading-state/thumbnail.png b/public/images/posts/react-query/react-query-loading-state/thumbnail.png new file mode 100644 index 0000000..f25f8c0 Binary files /dev/null and b/public/images/posts/react-query/react-query-loading-state/thumbnail.png differ diff --git a/public/images/posts/react/look-around-server-components/thumbnail.png b/public/images/posts/react/look-around-server-components/thumbnail.png new file mode 100644 index 0000000..643dcca Binary files /dev/null and b/public/images/posts/react/look-around-server-components/thumbnail.png differ diff --git a/public/images/posts/typescript/thought-typescript-implict-this/thumbnail.png b/public/images/posts/typescript/thought-typescript-implict-this/thumbnail.png new file mode 100644 index 0000000..9b5f1d7 Binary files /dev/null and b/public/images/posts/typescript/thought-typescript-implict-this/thumbnail.png differ diff --git a/public/images/posts/typescript/tsconfig-options/thumbnail.png b/public/images/posts/typescript/tsconfig-options/thumbnail.png new file mode 100644 index 0000000..b3f5ac7 Binary files /dev/null and b/public/images/posts/typescript/tsconfig-options/thumbnail.png differ diff --git a/public/images/posts/typescript/typescript-convariance-and-contravariance/thumbnail.png b/public/images/posts/typescript/typescript-convariance-and-contravariance/thumbnail.png new file mode 100644 index 0000000..84aa87a Binary files /dev/null and b/public/images/posts/typescript/typescript-convariance-and-contravariance/thumbnail.png differ diff --git a/public/images/posts/typescript/typescript-mapped-type/thumbnail.png b/public/images/posts/typescript/typescript-mapped-type/thumbnail.png new file mode 100644 index 0000000..3b65a9e Binary files /dev/null and b/public/images/posts/typescript/typescript-mapped-type/thumbnail.png differ diff --git a/public/images/posts/typescript/typescript-satisfies/thumbnail.png b/public/images/posts/typescript/typescript-satisfies/thumbnail.png new file mode 100644 index 0000000..7fa04b7 Binary files /dev/null and b/public/images/posts/typescript/typescript-satisfies/thumbnail.png differ diff --git a/public/images/posts/typescript/typescript-type-compatibility/thumbnail.png b/public/images/posts/typescript/typescript-type-compatibility/thumbnail.png new file mode 100644 index 0000000..7fb624c Binary files /dev/null and b/public/images/posts/typescript/typescript-type-compatibility/thumbnail.png differ diff --git a/public/images/posts/windows/windows-kill-process/already-use-port.png b/public/images/posts/windows/windows-kill-process/already-use-port.png new file mode 100644 index 0000000..af2ff25 Binary files /dev/null and b/public/images/posts/windows/windows-kill-process/already-use-port.png differ diff --git a/public/images/posts/windows/windows-kill-process/kill-success.png b/public/images/posts/windows/windows-kill-process/kill-success.png new file mode 100644 index 0000000..ac54a6b Binary files /dev/null and b/public/images/posts/windows/windows-kill-process/kill-success.png differ diff --git a/public/images/posts/windows/windows-kill-process/open-terminal.png b/public/images/posts/windows/windows-kill-process/open-terminal.png new file mode 100644 index 0000000..d8832c1 Binary files /dev/null and b/public/images/posts/windows/windows-kill-process/open-terminal.png differ diff --git a/public/images/posts/windows/windows-kill-process/process-list.png b/public/images/posts/windows/windows-kill-process/process-list.png new file mode 100644 index 0000000..fcfd2fe Binary files /dev/null and b/public/images/posts/windows/windows-kill-process/process-list.png differ diff --git a/public/images/posts/windows/windows-kill-process/thumbnail.jpg b/public/images/posts/windows/windows-kill-process/thumbnail.jpg new file mode 100644 index 0000000..f16b501 Binary files /dev/null and b/public/images/posts/windows/windows-kill-process/thumbnail.jpg differ diff --git a/src/components/Modules/Home/PostList/index.tsx b/src/components/Modules/Home/PostList/index.tsx index 748fe2c..21686cb 100644 --- a/src/components/Modules/Home/PostList/index.tsx +++ b/src/components/Modules/Home/PostList/index.tsx @@ -1,6 +1,5 @@ import { Post } from '@/contentlayer/generated'; import useStyledTheme from '@/hooks/theme/useStyledTheme'; -import { pageRoute } from '@/libs/models/route'; import isEmpty from '@/utils/is-packages/isEmpty'; import Flex from '@/components/Common/Flex'; import Section from '@/components/Common/Section'; @@ -39,7 +38,7 @@ const PostList = ({ category, posts, postsCount }: PostListProps) => { ))} diff --git a/src/components/Modules/Post/PrevNextPost/SimplePostItem/index.tsx b/src/components/Modules/Post/PrevNextPost/SimplePostItem/index.tsx index 6ea8e9d..2a33566 100644 --- a/src/components/Modules/Post/PrevNextPost/SimplePostItem/index.tsx +++ b/src/components/Modules/Post/PrevNextPost/SimplePostItem/index.tsx @@ -13,9 +13,9 @@ type SimplePostItemProps = Post & { }; const SimplePostItem = ({ - _raw, title, description, + url, thumbnail, itemType, }: SimplePostItemProps): JSX.Element => { @@ -25,7 +25,7 @@ const SimplePostItem = ({ ) => { const [postElement, setPostElement] = useState(null); - const { allPosts } = usePosts(); - - const currentPostIndex = allPosts.findIndex( - (allPost) => allPost._id === post?._id, - ); + const currentPostIndex = allPosts.findIndex(({ id }) => id === post?.id); // 최신순으로 정렬되므로 prevPost는 인덱스 + 1 const prevPost = allPosts[currentPostIndex + 1]; @@ -90,9 +86,9 @@ const PostDetailPage = ({ export const getStaticPaths: GetStaticPaths = async () => { return { - paths: allPosts.map(({ _raw }) => ({ + paths: allPosts.map(({ id }) => ({ params: { - slug: _raw.flattenedPath, + slug: id, }, })), @@ -105,8 +101,8 @@ export const getStaticProps: GetStaticProps<{ }> = async ({ params }) => { const postId = String(params?.slug || ''); - const post = allPosts.find(({ _raw }) => { - return _raw.flattenedPath === postId; + const post = allPosts.find(({ id }) => { + return id === postId; }); return { diff --git a/src/components/Pages/index.tsx b/src/components/Pages/index.tsx index 6d5b386..b054695 100644 --- a/src/components/Pages/index.tsx +++ b/src/components/Pages/index.tsx @@ -1,7 +1,9 @@ +import { useMemo } from 'react'; import { NextPage } from 'next'; import { useRouter } from 'next/router'; import { PER_PAGE_COUNT } from '@/constants/post'; -import usePosts from '@/hooks/post/usePosts'; +import chunkArray from '@/utils/array/chunkArray'; +import { categories, getPostsByCategory } from '@/utils/contentlayer'; import WelcomeBlog from '@/components/Modules/Home/WelcomeBlog'; import PostList from '@/components/Modules/Home/PostList'; import Flex from '@/components/Common/Flex'; @@ -18,12 +20,21 @@ const HomePage: NextPage = () => { const category = (query?.category || '전체') as string; const currentPage = Number(query?.page || 1); - const { filterPosts, chunkPosts, categories } = usePosts({ - category: category === '전체' ? '' : category, - }); - const postCategories = ['전체', ...categories]; + const posts = getPostsByCategory(category === '전체' ? '' : category); + + const totalPages = Math.ceil(posts.length / PER_PAGE_COUNT); + + const chunkPosts = useMemo( + () => + chunkArray({ + items: posts, + perItems: PER_PAGE_COUNT, + }), + [posts], + ); + const handlePageClick = (page: number): void => { push( { @@ -82,13 +93,13 @@ const HomePage: NextPage = () => { diff --git a/src/hooks/post/usePosts.ts b/src/hooks/post/usePosts.ts deleted file mode 100644 index c108eab..0000000 --- a/src/hooks/post/usePosts.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { useMemo } from 'react'; -import { Post, allPosts } from '@/contentlayer/generated'; -import { PER_PAGE_COUNT } from '@/constants/post'; -import chunkArray from '@/utils/array/chunkArray'; -import isEmpty from '@/utils/is-packages/isEmpty'; - -type PostsByCategory = Record; - -type Props = { - category?: string; -}; - -const usePosts = ({ category }: Props = {}) => { - const basePosts = [...allPosts].sort((prev, next) => { - return Date.parse(next.createdAt) - Date.parse(prev.createdAt); - }); - - const postsByCategory = useMemo(() => { - return basePosts.reduce((postsByCategory, post) => { - postsByCategory[post.category] = [ - ...(postsByCategory[post.category] || []), - post, - ]; - - return postsByCategory; - }, {} as PostsByCategory); - }, [basePosts]); - - const filterPosts = isEmpty(category) ? basePosts : postsByCategory[category]; - - const categories = useMemo(() => { - return Object.entries(postsByCategory) - .sort((prev, next) => { - const nextPosts = next[1]; - const prevPosts = prev[1]; - - return nextPosts.length - prevPosts.length; - }) - .map(([category]) => category); - }, [postsByCategory]); - - const chunkPosts = useMemo( - () => - chunkArray({ - items: filterPosts, - perItems: PER_PAGE_COUNT, - }), - [filterPosts], - ); - - return { - allPosts: basePosts, - filterPosts, - chunkPosts, - categories, - }; -}; - -export default usePosts; diff --git a/src/pages/sitemap.xml.ts b/src/pages/sitemap.xml.ts index 07f090b..fa46a93 100644 --- a/src/pages/sitemap.xml.ts +++ b/src/pages/sitemap.xml.ts @@ -1,7 +1,6 @@ import { NextPage, GetServerSideProps } from 'next'; import dayjs from 'dayjs'; import { allPosts } from '@/contentlayer/generated'; -import { pageRoute } from '@/libs/models/route'; import generateFullURL from '@/utils/string/generateFullURL'; const SitemapPage: NextPage = () => { @@ -17,12 +16,10 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { return Date.parse(next.createdAt) - Date.parse(prev.createdAt); }); - for (const { _raw, createdAt } of descOrderPosts) { + for (const { url, createdAt } of descOrderPosts) { pagesXMLString += ` - ${generateFullURL( - `${pageRoute.POSTS}/${encodeURIComponent(_raw.flattenedPath)}`, - )} + ${generateFullURL(url)} ${dayjs(createdAt).format('YYYY-MM-DDTHH:mm:ssZ')} `; diff --git a/src/utils/contentlayer/index.ts b/src/utils/contentlayer/index.ts new file mode 100644 index 0000000..6e68b96 --- /dev/null +++ b/src/utils/contentlayer/index.ts @@ -0,0 +1,30 @@ +import { allPosts as _allPosts, Post } from '@/contentlayer/generated'; +import isEmpty from '../is-packages/isEmpty'; + +type PostsByCategory = Record; + +export const allPosts = [..._allPosts].sort((prev, next) => { + return Date.parse(next.createdAt) - Date.parse(prev.createdAt); +}); + +export const postsByCategory = allPosts.reduce((postsByCategory, post) => { + postsByCategory[post.category] = [ + ...(postsByCategory[post.category] || []), + post, + ]; + + return postsByCategory; +}, {} as PostsByCategory); + +export const getPostsByCategory = (category: string | undefined) => { + return isEmpty(category) ? allPosts : postsByCategory[category]; +}; + +export const categories = Object.entries(postsByCategory) + .sort((prev, next) => { + const nextPosts = next[1]; + const prevPosts = prev[1]; + + return nextPosts.length - prevPosts.length; + }) + .map(([category]) => category);