<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>DevLog:-)</title>
    <link>https://developer-gaeppu.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 26 Jun 2026 21:36:16 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>hyeon200</managingEditor>
    <image>
      <title>DevLog:-)</title>
      <url>https://tistory1.daumcdn.net/tistory/6218564/attach/c4eb4e07ba484fb7ac8c4271837519db</url>
      <link>https://developer-gaeppu.tistory.com</link>
    </image>
    <item>
      <title>[Redis #1] 입문부터 실전까지: 인메모리 데이터베이스의 핵심 이해</title>
      <link>https://developer-gaeppu.tistory.com/249</link>
      <description>&lt;h2 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;1. 들어가며&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Redis는 대표적인 In-Memory Key-Value 저장소입니다. 빠른 속도와 다양한 데이터 구조를 제공하며, 실시간 데이터 처리, 인증 시스템, 캐싱, 세션 저장 등 다양한 분야에서 활발히 사용됩니다. 이 글에서는 Redis의 핵심 개념부터 실무에 적용하기 위한 실전 예제, 성능 고려사항, 확장 도구까지 체계적으로 정리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2. In-Memory Database란?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;In-Memory Database는 데이터를 디스크가 아닌 메모리(RAM)에 저장하는 방식의 데이터베이스입니다. 전통적인 RDBMS보다 훨씬 빠른 속도를 제공하지만, 메모리 기반이라는 특성상 운영 시 몇 가지 주의가 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;장점&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;낮은 지연 시간(Latency), 빠른 응답 속도&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;비동기 처리에 적합하며 TPS가 높은 시스템에 적합&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;간단한 구조와 빠른 학습 곡선&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;단점&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;데이터 휘발성 &amp;rarr; 별도의 영속성 설정 필요 (RDB, AOF)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;메모리 용량에 따라 저장 한계 존재&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;복잡한 쿼리 기능 부족&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Redis vs. 다른 In-Memory DB&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목Redis/MemcachedEhcache / Hazelcast&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;구조&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Key-Value + 다양한 구조체&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;단순 Key-Value&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;JVM 기반 객체 저장&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;데이터 타입&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;String, Hash, List, Set, ZSet 등&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;String only&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Java 객체 중심 캐시&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;영속성&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;RDB, AOF 지원&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;미지원&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;지원 (옵션에 따라 상이)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;스레드&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;단일 스레드(Event loop 기반)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;멀티 스레드&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;멀티 스레드&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;주요 활용&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;캐시, 세션, 인증, 랭킹, 메시지큐&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;캐시 중심&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;분산 캐시, 복제 클러스터&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3. Redis 데이터 타입&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Redis는 단순한 Key-Value 저장소를 넘어, 다양한 구조의 데이터를 저장하고 처리할 수 있는 구조를 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;주요 데이터 타입&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;String&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 기본 Key-Value 구조&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Hash&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 필드-값 쌍 저장. JSON과 유사한 구조&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;List&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: FIFO 큐 형태, 채팅 메시지, 알림 등에 활용&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Set&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 중복 없는 집합 구조&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Sorted Set (ZSet)&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 점수 기반 정렬. 랭킹 시스템 구현에 최적&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Bitmap / HyperLogLog / Stream&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 특수한 목적 (통계, 실시간 처리 등)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;4. Redis 설치 및 명령어&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;Docker로 Redis 실행하기&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;docker run --name redis -p 6379:6379 -d redis&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;기본 명령어&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;SET user:1:name &quot;Alice&quot;
GET user:1:name
HSET user:1 profile &quot;developer&quot;
LPUSH queue &quot;message&quot;
SADD tags &quot;redis&quot; &quot;spring&quot;
ZADD rank 100 &quot;user1&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;실시간 모니터링&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;redis-cli MONITOR&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;디버깅용으로만 사용. 실 운영 환경에서는 사용 금지 (부하 유발)&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;5. Spring Boot에서 Redis 활용&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;의존성 추가&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-data-redis'&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;설정 예시&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate&amp;lt;String, String&amp;gt; redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate&amp;lt;String, String&amp;gt; template = new RedisTemplate&amp;lt;&amp;gt;();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        return template;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;간단한 사용 예&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;redisTemplate.opsForValue().set(&quot;session:user:1&quot;, &quot;active&quot;, 10, TimeUnit.MINUTES);
String status = redisTemplate.opsForValue().get(&quot;session:user:1&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;6. 실무에서 반드시 알아야 할 고급 개념과 주의사항&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;시간 복잡도&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;대부분 명령은 O(1) 또는 O(logN)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;KEYS *&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;FLUSHALL&lt;/span&gt;&lt;span&gt; 등의 명령은 O(N) 이상 &amp;rarr; 운영 환경에서 절대 사용 금지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;대체: &lt;/span&gt;&lt;span&gt;SCAN&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;SSCAN&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ZSCAN&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;HSCAN&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;트랜잭션 (MULTI / EXEC)&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;MULTI&lt;/span&gt;&lt;span&gt;로 명령 큐잉 &amp;rarr; &lt;/span&gt;&lt;span&gt;EXEC&lt;/span&gt;&lt;span&gt;으로 원자성 있게 실행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;중간 오류 발생 시 전체 롤백 불가 &amp;rarr; 주의 필요&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;TTL (Time To Live)&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;key별로 만료 시간 설정 가능&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;만료되지 않으면 영구 저장됨 &amp;rarr; 캐시 누수 주의&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;메모리 관리 정책 (Eviction Policy)&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;설정 예: &lt;/span&gt;&lt;span&gt;maxmemory-policy allkeys-lru&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;지원 정책: &lt;/span&gt;&lt;span&gt;noeviction&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;volatile-lru&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;allkeys-lru&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;allkeys-random&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;volatile-ttl&lt;/span&gt;&lt;span&gt; 등&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;고가용성 / 확장&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Redis Sentinel&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 마스터 장애 감지 및 자동 failover&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Redis Cluster&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 샤딩 기반 수평 확장, 분산 환경에 적합&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;7. 함께 사용하면 좋은 도구들&lt;/span&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;도구&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;RedisInsight&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Redis Labs 제공 공식 GUI 클라이언트&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;Redisson&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;분산락, 캐시, 비동기 지원 등 고급 기능 포함&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;Spring Cache&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Redis와 연동되는 Spring 추상 캐시 애노테이션&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;Lettuce&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;비동기/논블로킹 지원 클라이언트&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;Prometheus + Grafana&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Redis 성능 모니터링 및 시각화 대시보드 구성을 위한 조합&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;8. 마무리: Redis는 캐시 그 이상이다&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Redis는 단순한 캐시 서버 이상의 역할을 합니다. 빠른 응답 속도, 구조화된 데이터 저장, 다양한 실시간 기능 덕분에 현대적인 백엔드 시스템의 필수 요소로 자리잡았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그러나 무조건 빠르다고 모든 것을 Redis에 넣어서는 안 됩니다. 데이터 영속성, 메모리 용량, TTL 관리, 명령어 시간 복잡도 등 Redis의 특성과 운영 이슈를 충분히 이해한 후 사용하는 것이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다음 글에서는 JWT 인증 시스템에서 Redis를 활용하여 refresh token을 화이트리스트 방식으로 관리한 실전 구현 사례를 소개합니다. 실전 코드와 함께 Redis의 장점을 어떻게 보안 아키텍처에 녹여내 보았습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <author>hyeon200</author>
      <guid isPermaLink="true">https://developer-gaeppu.tistory.com/249</guid>
      <comments>https://developer-gaeppu.tistory.com/249#entry249comment</comments>
      <pubDate>Mon, 21 Apr 2025 15:17:19 +0900</pubDate>
    </item>
    <item>
      <title>프로젝트 캠프 : Next.js 2기 - 2,3주 회고</title>
      <link>https://developer-gaeppu.tistory.com/224</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;719&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W8XaS/btsK17An5w4/6bQZeSbDwl52NyoU8nKmB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W8XaS/btsK17An5w4/6bQZeSbDwl52NyoU8nKmB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W8XaS/btsK17An5w4/6bQZeSbDwl52NyoU8nKmB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW8XaS%2FbtsK17An5w4%2F6bQZeSbDwl52NyoU8nKmB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;581&quot; height=&quot;327&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;719&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Learned&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;서버 컴포넌트와 클라이언트 컴포넌트의 차이&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;서버 컴포넌트 (RSC: React Server Component)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 페칭 역할.&lt;/li&gt;
&lt;li&gt;async 함수를 기본으로 사용할 수 있음.&lt;/li&gt;
&lt;li&gt;use client 키워드 없이 작성된 컴포넌트는 기본적으로 서버 컴포넌트.&lt;/li&gt;
&lt;li&gt;클라이언트 컴포넌트 아래에는 존재할 수 없음. 만약 클라이언트 컴포넌트 아래에 위치하면 더는 서버 컴포넌트로 작동하지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 컴포넌트 (RCC: React Client Component)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자와의 상호작용을 처리 (예: 버튼 클릭, 상태 관리 등).&lt;/li&gt;
&lt;li&gt;useState, useEffect 같은 클라이언트 훅 사용 가능.&lt;/li&gt;
&lt;li&gt;use client 키워드를 명시해야 클라이언트 컴포넌트로 동작.&lt;/li&gt;
&lt;li&gt;초기 렌더링은 서버에서 진행되지만, 이후 클라이언트 사이드에서 하이드레이션.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lF237/btsK2wGyT8n/rWg1ZlfLAxmxKX5zTkI8LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lF237/btsK2wGyT8n/rWg1ZlfLAxmxKX5zTkI8LK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lF237/btsK2wGyT8n/rWg1ZlfLAxmxKX5zTkI8LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlF237%2FbtsK2wGyT8n%2FrWg1ZlfLAxmxKX5zTkI8LK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;482&quot; height=&quot;274&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;컴포넌트 설계 전략&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;컴포넌트 트리 구성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 컴포넌트를 트리의 상위에 배치하여 최대한의 성능 최적화를 활용.&lt;/li&gt;
&lt;li&gt;클라이언트 컴포넌트는 트리의 하위(리프 노드)에 배치.&lt;/li&gt;
&lt;li&gt;서버 컴포넌트는 데이터 페칭 및 초기 렌더링을 담당하고, 클라이언트 컴포넌트는 필요한 동적 상호작용을 처리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;시스템 파일 구조&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;layout.tsx&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 경로를 감싸며 상위 구조를 담당.&lt;/li&gt;
&lt;li&gt;각 경로별로 중첩 가능.&lt;/li&gt;
&lt;li&gt;children 필수 삽입.&lt;/li&gt;
&lt;li&gt;공통 레이아웃 관리에 활용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;page.tsx&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라우트의 개별 페이지를 구성.&lt;/li&gt;
&lt;li&gt;필요한 경우 레이아웃과 독립적으로 메타데이터를 관리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;not-found.tsx&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;404 페이지 커스터마이징.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;error.tsx&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;경로별로 에러를 처리.&lt;/li&gt;
&lt;li&gt;error boundary 기능 내장.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;global-error.tsx&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 애플리케이션의 전역 에러 처리.&lt;/li&gt;
&lt;li&gt;프로덕션 모드에서만 활성화.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;loading.tsx&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로딩 상태 시 사용자 경험 향상을 위한 처리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;데이터 페칭 및 로딩&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;서버 컴포넌트&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;렌더링되기도 전에 데이터 페칭을 하기 떄문에 데이터 페칭 시 깜빡임 없음.&lt;/li&gt;
&lt;li&gt;초기 데이터를 빠르게 렌더링.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 컴포넌트&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트에서 데이터를 요청하므로 깜빡임이 발생 가능.&lt;/li&gt;
&lt;li&gt;loading.tsx를 사용하여 로딩 화면 표시.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;FAQ&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Q: 공통 사항은 layout에 넣는 것이 좋은가, page에 넣는 것이 좋은가?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공통 요소는 layout에 배치하여 중복을 줄이고 관리 용이성을 높임.&lt;/li&gt;
&lt;li&gt;개별 페이지의 특화된 UI는 page에서 처리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Q: Next.js에서 error boundary를 따로 작성해야 하나요?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;별도 작성 필요 없음. error.tsx 또는 global-error.tsx로 처리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Q: use client 키워드를 언제 사용해야 하나요?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 상호작용 또는 클라이언트 훅을 사용하는 컴포넌트에서만 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>hyeon200</author>
      <guid isPermaLink="true">https://developer-gaeppu.tistory.com/224</guid>
      <comments>https://developer-gaeppu.tistory.com/224#entry224comment</comments>
      <pubDate>Fri, 9 Aug 2024 23:22:00 +0900</pubDate>
    </item>
    <item>
      <title>SEO와 Next.js: 검색엔진 최적화와 효율적인 웹 렌더링 방법</title>
      <link>https://developer-gaeppu.tistory.com/223</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;프로젝트 캠프 : Next.js 2기를 수강하며 배운 내용을 정리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 개발에서 SEO(Search Engine Optimization)는 검색 결과에서 웹사이트 노출을 극대화하는 중요한 요소입니다. 특히 B2C 기업처럼 검색 트래픽이 중요한 비즈니스라면, 검색엔진이 잘 이해할 수 있는 구조로 웹사이트를 개발하는 것이 필수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;React&lt;/b&gt;와 같은 CSR(Client-Side Rendering) 프레임워크는 SEO에 불리할 수 있습니다. &lt;b&gt;SEO 최적화의 핵심 원리&lt;/b&gt;와 &lt;b&gt;Next.js&lt;/b&gt;를 활용해 이를 극복하는 방법을 정리했습니다. &lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CSR의 한계와 SEO 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSR 방식에서는 초기 HTML이 거의 비어 있습니다. React 사이트의 소스코드를 보면 &amp;lt;script&amp;gt; 태그만 포함된 것을 확인할 수 있습니다. 이는 검색엔진 크롤러가 콘텐츠를 인식하지 못하게 만들어 &lt;b&gt;SEO에 불리&lt;/b&gt;합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예: 검색엔진이 HTML을 분석할 때, 렌더링되지 않은 JavaScript는 콘텐츠로 포함되지 않습니다. 따라서 검색엔진은 페이지의 본문 내용을 인식하지 못할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SSR과 Next.js의 렌더링 방식&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Next.js는 어떻게 SEO를 지원할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Next.js&lt;/b&gt;는 SSR(Server-Side Rendering)을 지원해 SEO 문제를 효과적으로 해결합니다.&lt;br /&gt;다음은 Next.js의 렌더링 과정입니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;초기 요청 시, HTML/CSS 뼈대 전달&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 페이지를 요청하면 서버는 &lt;b&gt;HTML/CSS로만 구성된 페이지 뼈대&lt;/b&gt;를 전달합니다.&lt;/li&gt;
&lt;li&gt;이 단계에서 검색엔진 크롤러는 주요 콘텐츠를 바로 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하이드레이션(Hydration)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 서버로부터 JavaScript를 다운로드한 뒤 &lt;b&gt;하이드레이션&lt;/b&gt; 과정에서 인터랙션 가능 상태로 전환됩니다.&lt;/li&gt;
&lt;li&gt;즉, 처음에는 단순한 뼈대만 렌더링되지만, 이후 JavaScript가 적용되면서 완전한 기능이 활성화됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Next.js의 장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SEO 친화적:&lt;/b&gt; HTML 뼈대를 검색엔진이 쉽게 분석.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빠른 초기 렌더링:&lt;/b&gt; 서버에서 HTML만 전달하므로 로딩 속도가 빨라짐.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유저 경험 개선:&lt;/b&gt; 순차적으로 콘텐츠를 보여줌으로써 최적의 로딩 체감 제공.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SEO 최적화를 위한 개발 방법&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;SSR 사용:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Next.js와 같은 SSR 프레임워크를 활용해 검색엔진이 콘텐츠를 인식할 수 있도록 지원.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시멘틱 태그 활용:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;lt;header&amp;gt;, &amp;lt;main&amp;gt;, &amp;lt;article&amp;gt; 등의 태그를 적절히 사용해 콘텐츠의 구조를 명확히 표현.&lt;/li&gt;
&lt;li&gt;검색엔진 크롤러는 시멘틱 태그를 기반으로 콘텐츠의 중요도를 판단합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Lighthouse 점검:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a&gt;&lt;span&gt;Google&lt;/span&gt;&lt;span&gt; Lighthouse&lt;/span&gt;&lt;/a&gt;로 사이트의 SEO, 성능, 접근성을 점검 및 개선.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SEO, Best Practices, Accessibility 점수&lt;/b&gt;는 100점이 가능하며, &lt;b&gt;Performance&lt;/b&gt;는 로딩 속도에 따라 개선 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CSR과 SSR의 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분CSR (Client-Side Rendering)SSR (Server-Side Rendering)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;렌더링 주체&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;클라이언트가 렌더링&lt;/td&gt;
&lt;td&gt;서버가 렌더링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;SEO 친화성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;낮음 (HTML에 콘텐츠 없음)&lt;/td&gt;
&lt;td&gt;높음 (HTML에 콘텐츠 포함)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;초기 로딩 속도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;느림 (모든 작업을 클라이언트가 처리)&lt;/td&gt;
&lt;td&gt;빠름 (HTML 뼈대 먼저 제공)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;적합한 사용 사례&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;웹 애플리케이션 중심의 사이트&lt;/td&gt;
&lt;td&gt;검색 트래픽이 중요한 사이트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Lighthouse를 활용한 점수 최적화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js를 사용하면 아래 항목에서 높은 점수를 받을 수 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SEO:&lt;/b&gt; 시멘틱 태그와 SSR로 최적화.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Best Practices:&lt;/b&gt; 최신 웹 표준 준수.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Accessibility:&lt;/b&gt; 접근성 기준에 따라 UI 설계.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, &lt;b&gt;Performance 점수&lt;/b&gt;는 최적화가 까다로울 수 있습니다. 특히 대규모 트래픽 사이트에서는 모든 조건을 만족시키기 어렵지만, 이미지 최적화, 코드 스플리팅 등을 활용하면 개선할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SEO 최적화의 핵심:&lt;/b&gt; SSR이나 Next.js로 검색엔진이 콘텐츠를 쉽게 크롤링할 수 있도록 지원.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;효율적인 렌더링:&lt;/b&gt; 초기 HTML/CSS 뼈대를 제공한 뒤 하이드레이션으로 완전한 UI 완성.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Lighthouse 점수 활용:&lt;/b&gt; 성능, SEO, 접근성을 분석 및 개선.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색엔진 노출이 중요한 프로젝트를 준비 중이라면 Next.js를 고려하여 &lt;b&gt;빠른 렌더링과 SEO 최적화를 동시에 달성&lt;/b&gt;할 수 있습니다.&lt;/p&gt;</description>
      <category>프론트엔드</category>
      <author>hyeon200</author>
      <guid isPermaLink="true">https://developer-gaeppu.tistory.com/223</guid>
      <comments>https://developer-gaeppu.tistory.com/223#entry223comment</comments>
      <pubDate>Wed, 7 Aug 2024 01:51:13 +0900</pubDate>
    </item>
    <item>
      <title>프로젝트 캠프 : Next.js 2기 - 2주 회고</title>
      <link>https://developer-gaeppu.tistory.com/222</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EgqPh/btsIHaMAch7/T4fBEt6ylhxoUL7CituItK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EgqPh/btsIHaMAch7/T4fBEt6ylhxoUL7CituItK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EgqPh/btsIHaMAch7/T4fBEt6ylhxoUL7CituItK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEgqPh%2FbtsIHaMAch7%2FT4fBEt6ylhxoUL7CituItK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;581&quot; height=&quot;327&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Learned&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;리액트의 불변성과 상태 업데이트, 그리고 리듀서와 Context API&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;불변성이란 무엇인가?&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;불변성(Immutable)&lt;/b&gt;: 데이터의 원본을 변경하지 않고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;새로운 데이터&lt;/b&gt;를 생성하여 업데이트하는 것.&lt;/li&gt;
&lt;li&gt;리액트는 불변성을 기반으로 상태 업데이트를 감지한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기존 주소값이 변경되지 않으면&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;React는 상태가 업데이트되지 않았다고 파단한다.&lt;/li&gt;
&lt;li&gt;따라서 새로운 참조값(주소)을 전달해야 화면이 변경된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;왜 불변성을 유지해야 하는가?&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React는 상태의 참조값(주소)이 변경되는지 확인하여 리렌더링을 트리거한다.&lt;/li&gt;
&lt;li&gt;같은 참조값(주소)을 유지하면 React는 상태가 변경되지 않았다고 판단하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;화면에 변화가 발생하지 않는다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;이를 위해 객체나 배열 같은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;참조형 데이터&lt;/b&gt;는 새로운 값을 생성하여 업데이트해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;불변성을 지키는 방법 (참조형 데이터의 처리)&lt;/b&gt;&lt;/h4&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;pre id=&quot;code_1732969061465&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;//todos배열 업데이트를 해보자

let todos = [{ id: 1, text: &quot;아침 먹기&quot;, isCompleted: false }];

const addTodo = text =&amp;gt; {
  const newTodo = { id: todos.length + 1, text, isCompleted: false };
  todos = [...todos, newTodo]; // 새로운 배열로 업데이트 (불변성 유지)
};

const toggleTodo = id =&amp;gt; {
  todos = todos.map(todo =&amp;gt;
    todo.id === id ? { ...todo, isCompleted: !todo.isCompleted } : todo
  ); // map으로 새로운 배열 생성
};

const deleteTodo = id =&amp;gt; {
  todos = todos.filter(todo =&amp;gt; todo.id !== id); // filter로 새로운 배열 생성
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;push 대신 spread 연산자([...todos, newTodo])를 사용&lt;/b&gt;하여 새로운 배열을 생성.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;map과 filter 같은 내장 메서드&lt;/b&gt;로 새로운 배열을 만들어 상태를 업데이트.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;리액트에서 원시형 데이터와 참조형 데이터&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;원시형 데이터 (Primitive Types):&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;숫자, 문자열, 불리언 등. 값 자체로 비교 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1732969061472&quot; class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const [count, setCount] = useState(0);
setCount(1); // 새로운 값 1을 넣음 -&amp;gt; 리렌더링 발생&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;참조형 데이터 (Reference Types):&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체, 배열 등. 메모리 주소로 비교.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1732969061475&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const [todos, setTodos] = useState([{ id: 1, text: &quot;아침 먹기&quot;, isCompleted: false }]);
setTodos([...todos, { id: 2, text: &quot;점심 먹기&quot;, isCompleted: false }]); // 새로운 배열 전달&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;5.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;React.memo와 메모이제이션&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;React.memo&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트의 렌더링 비용을 줄이기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;props가 변경되지 않을 경우 리렌더링을 방지&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;고차 함수로, 컴포넌트를 인자로 받아&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;메모이제이션된 컴포넌트&lt;/b&gt;를 반환.&lt;/li&gt;
&lt;li&gt;주의:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;메모이제이션이 많아지면 성능 비용&lt;/b&gt;이 증가할 수 있으므로 적절히 사용해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1732969061479&quot; class=&quot;ebnf&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const MemoizedComponent = React.memo(MyComponent);&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;6.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;리듀서와 Context API&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;리듀서(Reducer):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상태를 관리하기 위한 함수.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;상태와 액션을 입력받아 새로운 상태를 반환&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;리액트의 useReducer 훅으로 구현 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1732969061481&quot; class=&quot;pf&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const reducer = (state, action) =&amp;gt; {
  switch (action.type) {
    case &quot;ADD_TODO&quot;:
      return { ...state, todos: [...state.todos, action.payload] };
    case &quot;TOGGLE_TODO&quot;:
      return {
        ...state,
        todos: state.todos.map(todo =&amp;gt;
          todo.id === action.payload ? { ...todo, isCompleted: !todo.isCompleted } : todo
        )
      };
    default:
      return state;
  }
};

const [state, dispatch] = useReducer(reducer, { todos: [] });&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;Context API:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트 트리에서 전역 상태를 관리할 때 사용.&lt;/li&gt;
&lt;li&gt;리듀서와 함께 사용하면 효율적으로 상태를 공유 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1732969061484&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const TodoContext = React.createContext();

const TodoProvider = ({ children }) =&amp;gt; {
  const [state, dispatch] = useReducer(reducer, { todos: [] });

  return (
    &amp;lt;TodoContext.Provider value={{ state, dispatch }}&amp;gt;
      {children}
    &amp;lt;/TodoContext.Provider&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;7.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;리액트에서 불변성 유지&lt;/b&gt;: 새로운 참조값을 생성해 상태 업데이트.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;참조형 데이터 처리&lt;/b&gt;: map, filter, spread 연산자로 새로운 데이터 생성.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;React.memo&lt;/b&gt;: 렌더링 최적화 도구로, 컴포넌트를 메모이제이션.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리듀서와 Context API&lt;/b&gt;: 상태 관리와 전역 공유를 위해 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;[유데미x스나이퍼팩토리] 프로젝트 캠프 : Next.js 2기 과정(B-log)&lt;/span&gt;&lt;/p&gt;</description>
      <category>프론트엔드</category>
      <author>hyeon200</author>
      <guid isPermaLink="true">https://developer-gaeppu.tistory.com/222</guid>
      <comments>https://developer-gaeppu.tistory.com/222#entry222comment</comments>
      <pubDate>Mon, 29 Jul 2024 01:40:20 +0900</pubDate>
    </item>
    <item>
      <title>프로젝트 캠프 : Next.js 2기 - 1주 회고</title>
      <link>https://developer-gaeppu.tistory.com/221</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EgqPh/btsIHaMAch7/T4fBEt6ylhxoUL7CituItK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EgqPh/btsIHaMAch7/T4fBEt6ylhxoUL7CituItK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EgqPh/btsIHaMAch7/T4fBEt6ylhxoUL7CituItK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEgqPh%2FbtsIHaMAch7%2FT4fBEt6ylhxoUL7CituItK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;581&quot; height=&quot;327&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Liked&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;운이 좋게 프로젝트 Next.js2기에 참여하게 되었다. 3주 동안 JavaScript, TypeScript, React, Next.js을 공부하고 기업 프로젝트를 진행하게 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;JavaScript와 React는 학습과 프로젝트 경험이 있어 복습을 하고 다시 개념을 다지기 좋은 기회라 생각이 든다. 또 Next.js은 단순 프로젝트 경험만 있어 아쉬웠는데 개념적인 부분부터 탄탄한 학습을 할 수 있을 거 같아 기대가 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;첫 주는 &lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;JavaScript&lt;/span&gt; 를 위주로 진행되었다. 강사님께서 정말 잘 설명해 주셔서 머릿속에서 떠돌고 있던 개념이 유기적으로 잘 정리된 느낌이어서 좋았다 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;모던 자바스크립트 스터디를 하면서 어려웠던 개념도 확실히 기초부터 하니 이해하게 된 거 같다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1주일 동안 배운 내용과 모던 자바스크립트 책을 한번 더 보면서 개념을 다시 정립하는 주간이었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Learned&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;첫 주는 javaScript와 TypeScript를 배웠다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 자바스크립트의 동작원리의 기본인 콜스택과 실행 컨텍스트부터 클래스 등 주요 개념을 순차적으로 다시 익힐 수 있어&amp;nbsp; 좋았다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이전부터 개인적으로 헷갈렸던 개념들은 추가로 공부해서 헷갈리는 키워드와 함께 비교하며 정리했다. &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n1k22/btsIGJ9I6Jt/EkGDcmzjS3ihCZvwSNuQNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n1k22/btsIGJ9I6Jt/EkGDcmzjS3ihCZvwSNuQNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n1k22/btsIGJ9I6Jt/EkGDcmzjS3ihCZvwSNuQNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn1k22%2FbtsIGJ9I6Jt%2FEkGDcmzjS3ihCZvwSNuQNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;265&quot; height=&quot;380&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;676&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;특히 타입스크립트를 공부하면서 프로젝트할 때 interface와 type 중 어떤 게 적절한지 고민을 하곤 했었는데 수업을 들으며 정리가 되었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;interface&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 객체 타입을 정의한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721574969029&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface IUser {
    name: string;
    age: number;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. extends키워드를 통해 확장(상속)할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721574894195&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Ijob extends IUser {
	title: string;
}

const user: Ijob = {
    title: &quot;developer&quot;,
    name: &quot;an&quot;,
	age: 20,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. 같은 이름으로 선언된 interface는 병합된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721574722456&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface IUser {
    name: string;
    readonly age: number;
    height?: number;
  }

  interface IUser {
    gender: string;
  }
  
  const user1: IUser = {//같은 이름의 인터페이스는 병합이 된다.
    name: &quot;kim&quot;,
    age: 20,
    gender: &quot;male&quot;,
    height: 100,
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;typeScript&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 객체를 포함한 모든 타입 정의가 가능하다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721575209822&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type TName = string; // primitive
type TAge = number;
type TPerson = [string, number, boolean]; // tuple
type TNumberString = string | number; // union&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &amp;amp;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기호를 사용해 확장(상속)할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721575343938&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const user: TUser = {
    name: &quot;kim&quot;,
    age:20
  };

type TJob = {
    title: &quot;d&quot;;
};

type TUserAndJob = TUser &amp;amp; TJob;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;3. 선언적 확장은 불가능하다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721575452678&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type TUser = {name: string; age: number; gender: &quot;male&quot; | &quot;female&quot;; };//Error:Duplicate identifier 'TUser'.
type TUser = {job: string }; //Error:Duplicate identifier 'TUser'.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;보통 위와 같은 특성으로 인해 객체 타입 정의 시에는 interface를 그 외의 타입 정의 시에는 &lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;type&lt;/span&gt;을 사용하게 된다. 프로젝트를 할 때 팀원분들과 미리 컨벤션을 약속하지 않고 개발을 하다 보면 이 둘을 구분하는 기준이 모호해질 때가 있다. 그래서 구체적으로 어떻게 둘을 구분해 사용하는 것이 좋을까 고민을 했었는데 강의를 들으며 고민을 해결할 수 있었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;386&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m31Lk/btsIHEfv8Xp/jazSrczZ4peKwG0YwV1K60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m31Lk/btsIHEfv8Xp/jazSrczZ4peKwG0YwV1K60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m31Lk/btsIHEfv8Xp/jazSrczZ4peKwG0YwV1K60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm31Lk%2FbtsIHEfv8Xp%2FjazSrczZ4peKwG0YwV1K60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;314&quot; height=&quot;169&quot; data-origin-width=&quot;386&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;364&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d0nTdR/btsIHbx07lt/ZOgBT4BO0h8ZWQF5fktz0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d0nTdR/btsIHbx07lt/ZOgBT4BO0h8ZWQF5fktz0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d0nTdR/btsIHbx07lt/ZOgBT4BO0h8ZWQF5fktz0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd0nTdR%2FbtsIHbx07lt%2FZOgBT4BO0h8ZWQF5fktz0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;324&quot; height=&quot;154&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;364&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;강사님께서는 &lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;type으로만으로도&lt;/span&gt; 개발을 하신다고 하셨다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt; &lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;type&lt;/span&gt;은 상속도 가능하고, 가독성 특히 위와 같이 개발 환경 툴팁에서의 강점이 있었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt; &lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;type&lt;/span&gt;은 interface&lt;/span&gt;와 달리 타입이 명시적으로 보여 의미파악이 쉽고 개발에 용의 하다는 이점이 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;사실상 모든 타입을 &lt;span style=&quot;font-family: 'Nanum Gothic'; background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;type&lt;/span&gt;만으로 정의해도 괜찮&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;다는 것이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; 결국에 중요한 건 팀 협업할 때 서로 협의한 타입 정의 규칙에 맞춰 일관성 있는 코드를 유지하는 것이 중요한 거 같다. 또한 코드를 작성할 때 타당한 근거나 이유를 알고 개발하는 것이 도움이 많이 된다는 것을 느꼈다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Lacked&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYLIs8/btsII02YKUh/5teNs1dy737XKksUhSuPk1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYLIs8/btsII02YKUh/5teNs1dy737XKksUhSuPk1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYLIs8/btsII02YKUh/5teNs1dy737XKksUhSuPk1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYLIs8%2FbtsII02YKUh%2F5teNs1dy737XKksUhSuPk1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;332&quot; height=&quot;205&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;온라인에 익숙하다가 오프라인 출퇴근을 하려니 조금 피곤했다 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 때문에 이번주 사이드 프로젝트 작업을 많이 못한 거 같아 컨디션 관리에 신경 쓰려한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그날 무리하는 것보다 적절한 수면이 다음날 학습과 작업 효율을 &lt;span style=&quot;font-family: 'Nanum Gothic'; color: #333333; text-align: start;&quot;&gt;훨씬&lt;/span&gt; 높인다는 것을 깨달았고 낮에 최대한 집중하는 게 중요하다는 생각이 든다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Longed for&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;다시 정리한 자바스크립트와 타입스크립트를 바탕으로 노션 클로닝 프로젝트를 타입스크립트로 다시 개발해볼까 한다. 이번에는 파일 구조도 전체적으로 변경하고 효율적인 방향을 많이 고민해서 개발해보고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;본 후기는 본 후기는 [유데미x스나이퍼팩토리] 프로젝트 캠프 : Next.js 2기 과정(B-log) 리뷰로 작성되었습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Next.js</category>
      <category>udemy</category>
      <category>미래내일일경험</category>
      <category>부트캠프</category>
      <category>스나이퍼 팩토리</category>
      <category>웅진씽크빅</category>
      <category>유데미</category>
      <category>인사이드아웃</category>
      <category>프로젝트캠프</category>
      <category>프론트엔드개발자양성과정</category>
      <author>hyeon200</author>
      <guid isPermaLink="true">https://developer-gaeppu.tistory.com/221</guid>
      <comments>https://developer-gaeppu.tistory.com/221#entry221comment</comments>
      <pubDate>Sun, 21 Jul 2024 17:36:46 +0900</pubDate>
    </item>
    <item>
      <title>Strict Mode로 인한 토스트 두 번 출력 문제 해결</title>
      <link>https://developer-gaeppu.tistory.com/220</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;picky 프로젝트 개발시에 토스트가 두 번씩 출력되는 문제를 발생했습니다.서칭 결과 이 문제는 React의 &lt;b&gt;개발 단계에서만 발생&lt;/b&gt;하는 현상으로, Strict Mode의 특성 때문에 생기는 일입니다. 이 글에서는 왜 이런 현상이 발생하는지, 그리고 이를 어떻게 해결할 수 있는지에 대해 알아보았습니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Strict Mode란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 Strict Mode는 개발 단계에서 잠재적인 문제를 감지하고 경고를 제공하기 위해 사용됩니다. React 공식 문서에 따르면, Strict Mode는 &lt;b&gt;안전하지 않은 코드 패턴을 확인하고 경고&lt;/b&gt;하며, 일부 함수형 컴포넌트의 &lt;b&gt;이펙트를 두 번 호출&lt;/b&gt;하는 동작을 포함합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Strict Mode 주요 기능&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트의 사이드 이펙트 감지&lt;/li&gt;
&lt;li&gt;useEffect 클린업 함수 확인&lt;/li&gt;
&lt;li&gt;React 렌더링 관련 문제 탐지&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;문제 원인: 토스트 두 번 출력&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Strict Mode에서 &lt;b&gt;React는 컴포넌트 마운트 및 언마운트 과정을 두 번 수행&lt;/b&gt;하여 예상치 못한 결과를 발생시킬 수 있습니다. 예를 들어, 토스트 메시지를 출력하는 로직이 아래와 같다고 가정해 봅시다:&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;pre id=&quot;code_1732973284395&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { toast } from 'react-toastify';

function ExampleComponent() {
  React.useEffect(() =&amp;gt; {
    toast('Hello, World!');
  }, []);

  return &amp;lt;div&amp;gt;Example Component&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Strict Mode에서는 useEffect가 두 번 호출되어, 토스트가 두 번 출력됩니다. 이는 배포 환경에서는 발생하지 않지만, 개발 단계에서 혼란을 줄 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;해결 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Strict Mode 비활성화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간단한 해결 방법은 Strict Mode를 비활성화하는 것입니다. next.config.js에서 reactStrictMode 옵션을 false로 설정하면 됩니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1732973335903&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// next.config.js
module.exports = {
  reactStrictMode: false,
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;!주의&lt;/b&gt;: Strict Mode는 React의 최적화 및 안전한 코드 작성을 돕기 때문에, 단순히 비활성화하는 것은 권장되지 않습니다. 이 방법은 문제의 임시 해결에만 적합합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 상태 플래그를 사용한 방지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRef를 활용해 첫 번째 렌더링 이후에만 동작하도록 코드를 수정할 수 있습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1732973379081&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { toast } from 'react-toastify';
import React, { useEffect, useRef } from 'react';

function ExampleComponent() {
  const didMount = useRef(false);

  useEffect(() =&amp;gt; {
    if (didMount.current) {
      toast('Hello, World!');
    } else {
      didMount.current = true;
    }
  }, []);

  return &amp;lt;div&amp;gt;Example Component&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 첫 번째 렌더링에서는 아무 작업도 하지 않고, 이후 렌더링부터 동작하도록 합니다. 이를 통해 Strict Mode에서도 토스트가 한 번만 출력되도록 할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. ToastContainer 설정으로 문제 완화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-toastify에서 ToastContainer의 설정을 통해 중복 토스트 메시지를 방지할 수도 있습니다. 예를 들어, preventDuplicates 옵션을 사용하는 방식입니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1732973401194&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { ToastContainer, toast } from 'react-toastify';

function ExampleComponent() {
  useEffect(() =&amp;gt; {
    toast('Hello, World!');
  }, []);

  return (
    &amp;lt;div&amp;gt;
      Example Component
      &amp;lt;ToastContainer preventDuplicates /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 동일한 메시지가 중복 출력되는 것을 방지합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결론!&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Strict Mode는 개발 단계에서 더 안전한 애플리케이션을 작성하도록 돕지만, 예기치 않은 동작을 초래할 수 있기에 이를 해결하기 위해 아래 방법을 고려할 수 있습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Strict Mode 비활성화&lt;/li&gt;
&lt;li&gt;상태 플래그(useRef)를 사용해 첫 번째 호출을 무시&lt;/li&gt;
&lt;li&gt;react-toastify의 preventDuplicates 옵션 활용&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;896&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kY3ll/btsIzzzczq6/gnr3JdU3uJVv1tAjP77evK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kY3ll/btsIzzzczq6/gnr3JdU3uJVv1tAjP77evK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kY3ll/btsIzzzczq6/gnr3JdU3uJVv1tAjP77evK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkY3ll%2FbtsIzzzczq6%2Fgnr3JdU3uJVv1tAjP77evK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;249&quot; height=&quot;386&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;896&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;901&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKp33V/btsIyJvs9Qw/549BaK3tFKXCJUqlNPmup0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKp33V/btsIyJvs9Qw/549BaK3tFKXCJUqlNPmup0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKp33V/btsIyJvs9Qw/549BaK3tFKXCJUqlNPmup0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKp33V%2FbtsIyJvs9Qw%2F549BaK3tFKXCJUqlNPmup0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;261&quot; height=&quot;395&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;901&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>hyeon200</author>
      <guid isPermaLink="true">https://developer-gaeppu.tistory.com/220</guid>
      <comments>https://developer-gaeppu.tistory.com/220#entry220comment</comments>
      <pubDate>Mon, 15 Jul 2024 17:04:58 +0900</pubDate>
    </item>
    <item>
      <title>NEXT.js 메타데이터, 오픈그래이프 적용기(2) -  local환경에서 확인하는 법</title>
      <link>https://developer-gaeppu.tistory.com/219</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 투표플랫폼 Picky프로젝트에 메타데이터를 적용하면서 난감했던 부분은 바로 잘 적용되었는지 검증하는 과정이었습니다. sns 공유상 메타데이터가 잘 적용되었는지 확인하기 위해서는 배포 후의 결과물을 공유하는 과정이 필요했고 이를 위해서는 한번의 테스팅을 위해&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;commit &amp;rarr; push &amp;rarr; PR &amp;rarr; merge &amp;rarr; main building &amp;rarr; sns 공유&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 과정을 거쳐야했습니다. 근데 여기서 테스팅 결과 어떠한 문제로 적용이 잘 안되었다면 이 과정을 계속 반복해야한다는 것이 너무 번거롭게 느껴졌습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 로컬 개발환경에서 쉽게 메타데이터 적용결과를 확인할 수 있는 방법을 구글링을 통해 발견했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 방법 중 제가 채택한 방법을 설명드리겠습니다! &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Chrome Extension 활용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 환경에서 메타데이터를 빠르게 확인하기 위해 임시배포를 할 수 있는&amp;nbsp;Chrome Extension을 활용해 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 사용한 Extension은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://chromewebstore.google.com/detail/localhost-open-graph-chec/gcbnmkhkglonipggglncobhklaegphgn&quot;&gt;Localhost Open Graph Check&lt;/a&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Extension은 로컬 서버에서 임시로 배포할 수 있게 해주며, 이 링크를 공유해서 Open Graph 태그가 제대로 적용되었는지를 쉽게 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Chrome Web Store에서 Extension을 설치합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;87&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pjjdK/btsIU9tGMvR/MkfUfRJx1vIAArfj2ZGy10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pjjdK/btsIU9tGMvR/MkfUfRJx1vIAArfj2ZGy10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pjjdK/btsIU9tGMvR/MkfUfRJx1vIAArfj2ZGy10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpjjdK%2FbtsIU9tGMvR%2FMkfUfRJx1vIAArfj2ZGy10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;87&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;87&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 로컬 개발 환경에서 Extension을 누르면 바로 배포 결과 페이지가 나오는 것을 확인하실 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3DleC/btsIVbrxIuk/RDnMEgAfTJEvV6KBwXi0g0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3DleC/btsIVbrxIuk/RDnMEgAfTJEvV6KBwXi0g0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3DleC/btsIVbrxIuk/RDnMEgAfTJEvV6KBwXi0g0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3DleC%2FbtsIVbrxIuk%2FRDnMEgAfTJEvV6KBwXi0g0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;294&quot; height=&quot;186&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;315&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dZ1KAQ/btsIXmSqEPe/8wTMGajPSNcgHV5UqVODc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dZ1KAQ/btsIXmSqEPe/8wTMGajPSNcgHV5UqVODc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZ1KAQ/btsIXmSqEPe/8wTMGajPSNcgHV5UqVODc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdZ1KAQ%2FbtsIXmSqEPe%2F8wTMGajPSNcgHV5UqVODc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;364&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임시 배포의 링크가 제공되는 것을 확인하실 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 링크를 공유해서 메타 정보를 확인하면 될 것이라 생각했지만, 임시 배포 링크를 사용했을 때 이미지가 제대로 표시되지 않는 문제를 발견했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 다음과 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;프로젝트 내에 있는 이미지 데이터가 로컬 서버에 호스팅되어 있는 경우(ex.http://localhost:3000/picky/thumbnail.png), 이 이미지 파일들은 로컬 네트워크 내부에서만 접근할 수 있습니다.&lt;br /&gt;외부에서 접근하려고 하면 로컬 호스트의 특성상 접근이 차단되기 때문에, 소셜 미디어 플랫폼이나 다른 외부 서비스가 로컬 이미지를 불러올 수 없습니다. 따라서 로컬 환경에서 메타 태그에 설정된 이미지가 외부에서 노출되지 않습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 활용할 수 있는 방법은 사용중인 컴퓨터에서 local Open Graph Preview 사이트 사용하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost에 접근이 가능한 동일한 컴퓨터 내에서 사이트를 이용하기 때문에 로컬 og 이미지도 확인이 가능합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Open Graph Preview 사이트 사용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://www.opengraph.xyz/&quot;&gt;Open Graph Preview&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;사이트를 사용했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 임시 배포 링크를 얻었다면, 사이트에 입력 후 check Website 버튼을 눌러 결과를 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cThCIb/btsIW3Mm1Ov/n1Aucz8o2KeNuA2icJK5LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cThCIb/btsIW3Mm1Ov/n1Aucz8o2KeNuA2icJK5LK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cThCIb/btsIW3Mm1Ov/n1Aucz8o2KeNuA2icJK5LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcThCIb%2FbtsIW3Mm1Ov%2Fn1Aucz8o2KeNuA2icJK5LK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;747&quot; height=&quot;192&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이 사이트를 사용하면 다양한 플랫폼에서 링크가 어떻게 표시되는지 한눈에 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;856&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyLkmY/btsIU9AvOhK/KgH3AbocTI6KfK8K4PKGBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyLkmY/btsIU9AvOhK/KgH3AbocTI6KfK8K4PKGBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyLkmY/btsIU9AvOhK/KgH3AbocTI6KfK8K4PKGBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyLkmY%2FbtsIU9AvOhK%2FKgH3AbocTI6KfK8K4PKGBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;238&quot; height=&quot;574&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;856&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;동적 메타 태그 확인하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 페이지의 메타 태그를 확인하고 싶다면, 해당 라우트 페이지에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://chromewebstore.google.com/detail/localhost-open-graph-chec/gcbnmkhkglonipggglncobhklaegphgn&quot;&gt;Localhost Open Graph Check&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;임시 배포를 한 후 그 링크를 Preview 사이트에 적용하면 됩니다. 이렇게 하면 동적 콘텐츠의 메타데이터도 쉽게 검증할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;298&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PTJFz/btsIWylKIAN/IipCcScV9cKO6Fp7nwnxH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PTJFz/btsIWylKIAN/IipCcScV9cKO6Fp7nwnxH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PTJFz/btsIWylKIAN/IipCcScV9cKO6Fp7nwnxH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPTJFz%2FbtsIWylKIAN%2FIipCcScV9cKO6Fp7nwnxH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;220&quot; height=&quot;604&quot; data-origin-width=&quot;298&quot; data-origin-height=&quot;818&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 배포 후에도 제대로 적용된 것을 확인하실 수 있습니다.&amp;nbsp;&lt;/p&gt;</description>
      <author>hyeon200</author>
      <guid isPermaLink="true">https://developer-gaeppu.tistory.com/219</guid>
      <comments>https://developer-gaeppu.tistory.com/219#entry219comment</comments>
      <pubDate>Thu, 11 Jul 2024 14:34:45 +0900</pubDate>
    </item>
    <item>
      <title>NEXT.js 메타데이터, 오픈그래이프 적용기(1)  - 다이나믹 메타데이터 적용</title>
      <link>https://developer-gaeppu.tistory.com/218</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Next.js는 서버 사이드 렌더링(SSR)과 정적 사이트 생성(SSG)을 손쉽게 구현할 수 있도록 도와줍니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Next.js를 사용할 때 중요한 요소 중 하나가 메타데이터(metadata)인데요.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최근 투표 플랫폼 Picky 프로젝트에서 Metadata를 적용하게 되어 적용기를 작성하게 되었습니다!&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Metadata란?&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;메타데이터(metadata)는 웹 페이지에 대한 정보를 설명하는 데이터입니다. 보통 HTML 문서의 &amp;lt;head&amp;gt; 태그 내에 포함되며, 웹 브라우저와 검색 엔진이 페이지를 이해하고 적절히 처리할 수 있도록 도와줍니다. 대표적인 메타데이터 요소로는 title, meta 태그의 description, keywords 속성 등이 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Metadata와 SEO&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SEO(검색 엔진 최적화)는 웹사이트가 검색 엔진 결과 페이지에서 더 높은 순위를 차지하게 하는 일련의 전략과 기술입니다. 메타데이터는 SEO의 중요한 구성 요소 중 하나로, 검색 엔진이 페이지의 내용을 이해하고 인덱싱하는 데 도움을 줍니다. 적절한 메타데이터 설정을 통해 검색 결과에서의 가시성을 높일 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Open Graph란?&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Open Graph는 Facebook에서 개발한 프로토콜로, 웹 페이지가 소셜 미디어 플랫폼에서 어떻게 보여질지를 정의합니다. Open Graph 메타데이터는 다음과 같이 소셜 미디어에서 공유될 때 웹 페이지의 제목, 설명, 이미지 등을 지정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yedbu/btsIVbdVUBW/V99i1MpbSpLTIJebylZBG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yedbu/btsIVbdVUBW/V99i1MpbSpLTIJebylZBG1/img.png&quot; style=&quot;width: 54.015%; margin-right: 10px;&quot; width=&quot;252&quot; height=&quot;177&quot; data-origin-width=&quot;418&quot; data-origin-height=&quot;293&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;54.65&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yedbu/btsIVbdVUBW/V99i1MpbSpLTIJebylZBG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyedbu%2FbtsIVbdVUBW%2FV99i1MpbSpLTIJebylZBG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;418&quot; height=&quot;293&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o12Rn/btsIVE72i4O/lZfdJZiGXkOJQrAsqZU730/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o12Rn/btsIVE72i4O/lZfdJZiGXkOJQrAsqZU730/img.png&quot; style=&quot;width: 44.8222%;&quot; width=&quot;195&quot; height=&quot;165&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;272&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;45.35&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o12Rn/btsIVE72i4O/lZfdJZiGXkOJQrAsqZU730/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo12Rn%2FbtsIVE72i4O%2FlZfdJZiGXkOJQrAsqZU730%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;322&quot; height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Dynamic Meta란?&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Dynamic Meta는 웹 페이지의 메타데이터를 동적으로 생성하는 기술을 의미합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 블로그 포스트와 같은 콘텐츠에서는 각 포스트마다 다른 제목, 설명, 이미지를 메타데이터로 설정해야 합니다. 이를 위해 서버 사이드 렌더링을 사용하거나 클라이언트 사이드에서 동적으로 메타데이터를 설정할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저는 picky가 게시글 서비스인 만큼 페이지별로 다른 정보를 제공할 수 있도록 동적 메타데이터를 적용했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;이제 Next.js에 Metadata를 적용해 볼까요?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Next.js에서 Static Metadata 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Next.js를 사용하지 않았던 프로젝트에서는 주로 index.html 파일의 &amp;lt;head&amp;gt; 태그 내에 메타데이터를 설정했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이와 달리 Next.js에서는 메타데이터 설정에 아주 용이한 기능을 갖추고 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;바로 metadata 객체를 활용하는 것입니다!&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://nextjs.org/docs/app/building-your-application/optimizing/metadata&quot;&gt;Next.js 공식 문서&lt;/a&gt;를 참고하면 쉽게 이해할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Next.js에서는 layout.js나 page.js 파일에 metadata 객체를 이용하여 정적 메타데이터를 설정할 수 있습니다. 다음은 정적 메타데이터를 설정하는 간단한 예시입니다.&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1722968226494&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// layout.js | page.tsx
import type { Metadata } from 'next';
 
export const metadata: Metadata = {
  title: 'picky',
  description: '투표를 통한 고민 해결 커뮤니티'
};
 
export default function Page() {
  return &amp;lt;div&amp;gt;Content&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Dynamic Metadata 설정&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Next.js의 metadata 객체를 이용해 동적 메타데이터도 쉽게 구현할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;metadata 객체를 반환하는 generateMetadata 함수를 활용하는 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;generateMetadata 함수를 사용하면 페이지별 데이터를 받아와서 동적으로 메타데이터를 설정할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1722968226500&quot; class=&quot;qml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// app/products/[id]/page.tsx
import type { Metadata, ResolvingMetadata } from 'next'
 
type Props = {
  params: { id: string }
  searchParams: { [key: string]: string | string[] | undefined }
}
 
export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise&amp;lt;Metadata&amp;gt; {
  // 라우트 파라미터 읽기
  const id = params.id;
 
  // 데이터 가져오기
  const product = await fetch(`https://api.example.com/products/${id}`).then((res) =&amp;gt; res.json());
 
  // 부모 메타데이터를 확장
  const previousImages = (await parent).openGraph?.images || [];
 
  return {
    title: product.title,
    openGraph: {
      images: ['/some-specific-page-image.jpg', ...previousImages],
    },
  };
}
 
export default function Page({ params, searchParams }: Props) {
  return &amp;lt;div&amp;gt;Product Details&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;picky프로젝트에 Metadata 적용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Static Metadata&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저는 위에 내용을 바탕으로 프로젝트에 Metadata 생성 유틸리티 함수를 구현해 적극 활용했습니다!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정적 메타데이터를 getMetadata 함수를 구현하여 layout.tsx에 불러왔습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1722968226511&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// app/layout.tsx
import { getMetadata } from '../utils/getMetadata';

export const metadata = getMetadata();&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1722968226514&quot; class=&quot;properties&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// utils/getMetadata.ts
import type { Metadata } from 'next';

export const getMetadata = (metadataProps?: GetMetadataProps) =&amp;gt; {
  const { title, description, asPath, image, icon } = metadataProps || {};

  const TITLE = title ? `${title} | picky` : META.title;
  const DESCRIPTION = description || META.description;
  const PAGE_URL = asPath ? META.url + asPath : META.url;
  const IMAGE = image || META.image;
  const ICON = icon || META.icon;

  const metadata: Metadata = {
    metadataBase: new URL(META.url),
    alternates: {
      canonical: PAGE_URL,
    },
    manifest: '/manifest.json',
    title: TITLE,
    description: DESCRIPTION,
    keywords: [...META.keyword],
    icons: {
      icon: ICON,
    },
    openGraph: {
      title: TITLE,
      description: DESCRIPTION,
      siteName: TITLE,
      locale: 'ko_KR',
      type: 'website',
      url: PAGE_URL,
      images: {
        url: IMAGE,
      },
    },
    twitter: {
      title: TITLE,
      description: DESCRIPTION,
      images: {
        url: IMAGE,
      },
    },
  };

  return metadata;
};
// META는 상수화된 메타정보 객체입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 layout.tsx에서 Metadata를 따로 유틸함수로 분리하여 layout.tsx의 코드 길이를 단축하고 기능별 유지보수 측면에서도 효율적이었습니다. 또한 getMetaData 유틸리티 함수를 재활용하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;추후&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;동적 메타데이터 유틸리티 함수도 쉽게 구현할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;793&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d4I1PP/btsIU9HdTZv/85ItVP8qx7VfsNy7ePzkq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d4I1PP/btsIU9HdTZv/85ItVP8qx7VfsNy7ePzkq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d4I1PP/btsIU9HdTZv/85ItVP8qx7VfsNy7ePzkq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd4I1PP%2FbtsIU9HdTZv%2F85ItVP8qx7VfsNy7ePzkq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;354&quot; height=&quot;240&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;793&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Dynamic Metadata&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정적 메타 정보를 담고 있는 getMetadata를 불러와 getGenerateMetadata를 만들어 동적 메타데이터를 설정했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;게시글 페이지 url에 params에서 postId를 가져오고 그에 맞는 데이터를 받아와 메타데이터에 게시글 별로 동적으로 적용되도록 했습니다.&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1722968226534&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// utils/getGenerateMetadata.ts
const RESULT_MESSAGE = '투표 결과를 확인하세요.';
const VOTE_MESSAGE = 'picky에서 투표해보세요';

export const getGenerateMetadata = () =&amp;gt; async ({ params }: { params: { postId: string } }) =&amp;gt; {
  try {
    const postId = params.postId;
    const response = await getVoteDetail(postId);

    if (!response || !response.body) {
      return;
    }

    const { voteTitle, hasVoted, voteOptions, terminated: isTerminated } = response.body;
    const thumbnailImageUrl = voteOptions?.map(({ voteOptionImageUrl }) =&amp;gt; voteOptionImageUrl).filter(url =&amp;gt; url)[0];
    const title = `${voteTitle}`;
    const description = hasVoted || isTerminated ? RESULT_MESSAGE : VOTE_MESSAGE;

    return getMetadata({
      title,
      description,
      asPath: `/result/${postId}`,
      image: thumbnailImageUrl,
    });
  } catch (error) {
    console.error('Failed to generate metadata:', error);
    return;
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동적으로 데이터를 받아 Open Graph 이미지를 게시글 이미지로 변경하고, 투표 진행 중인 게시글이라면 'picky에서 투표해보세요'를, 투표 종료 게시글이라면 '투표 결과를 확인하세요'를 띄워 더 나은 사용자 경험에 신경썼습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA1zBt/btsIV7vnz2I/GkQCKGXCJh87HDukRDffUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA1zBt/btsIV7vnz2I/GkQCKGXCJh87HDukRDffUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA1zBt/btsIV7vnz2I/GkQCKGXCJh87HDukRDffUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA1zBt%2FbtsIV7vnz2I%2FGkQCKGXCJh87HDukRDffUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;322&quot; height=&quot;436&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이와 같이 Next.js에서는 metadata 객체를 이용해 정적 및 동적 메타데이터를 쉽게 설정할 수 있습니다. 이를 통해 SEO와 소셜 미디어에서의 콘텐츠 미리보기 최적화를 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;116&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv95qQ/btsIWgllqk4/CAWkw9zdatjd3tqn1KXJhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv95qQ/btsIWgllqk4/CAWkw9zdatjd3tqn1KXJhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv95qQ/btsIWgllqk4/CAWkw9zdatjd3tqn1KXJhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv95qQ%2FbtsIWgllqk4%2FCAWkw9zdatjd3tqn1KXJhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;93&quot; height=&quot;110&quot; data-origin-width=&quot;116&quot; data-origin-height=&quot;137&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Next.js의 새로운 문법이 처음에는 낯설었지만 익숙해지면서 훨씬 효율적으로 메타데이터를 관리할 수 있었고 Next.js의 장점을 다시 체감하게 된 계기가 되었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가적으로 메타데이터 작업을 할때 번거로운 배포 과정 없이&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;브라우저 확장 프로그램과 미리보기 웹사이트를 적절히 활용하여 빠른 검증과 개발이 가능합니다!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;자세한 내용은 다음 포스팅에서 확인하실 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1722968354349&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;NEXT.js 메타데이터, 오픈그래이프 적용기(2) -  local환경에서 확인하는 법&quot; data-og-description=&quot;이번 투표플랫폼 Picky프로젝트에 메타데이터를 적용하면서 난감했던 부분은 바로 잘 적용되었는지 검증하는 과정이었습니다. sns 공유상 메타데이터가 잘 적용되었는지 확인하기 위해서는 배포&quot; data-og-host=&quot;developer-gaeppu.tistory.com&quot; data-og-source-url=&quot;https://developer-gaeppu.tistory.com/219&quot; data-og-url=&quot;https://developer-gaeppu.tistory.com/219&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dd7Ld7/hyWKJfPYvz/HwskKQ6EPtGLkvkIwIuZD0/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/Ddzuf/hyWKKy6GrA/RuAM8NIkzdkQemkTc5n8r1/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/bO4cTl/hyWKHbkzOZ/fezZKSnm1KEPFJB4cI7F5k/img.jpg?width=564&amp;amp;height=564&amp;amp;face=0_0_564_564&quot;&gt;&lt;a href=&quot;https://developer-gaeppu.tistory.com/219&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer-gaeppu.tistory.com/219&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dd7Ld7/hyWKJfPYvz/HwskKQ6EPtGLkvkIwIuZD0/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/Ddzuf/hyWKKy6GrA/RuAM8NIkzdkQemkTc5n8r1/img.png?width=225&amp;amp;height=225&amp;amp;face=0_0_225_225,https://scrap.kakaocdn.net/dn/bO4cTl/hyWKHbkzOZ/fezZKSnm1KEPFJB4cI7F5k/img.jpg?width=564&amp;amp;height=564&amp;amp;face=0_0_564_564');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;NEXT.js 메타데이터, 오픈그래이프 적용기(2) - local환경에서 확인하는 법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 투표플랫폼 Picky프로젝트에 메타데이터를 적용하면서 난감했던 부분은 바로 잘 적용되었는지 검증하는 과정이었습니다. sns 공유상 메타데이터가 잘 적용되었는지 확인하기 위해서는 배포&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer-gaeppu.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>hyeon200</author>
      <guid isPermaLink="true">https://developer-gaeppu.tistory.com/218</guid>
      <comments>https://developer-gaeppu.tistory.com/218#entry218comment</comments>
      <pubDate>Thu, 11 Jul 2024 14:34:24 +0900</pubDate>
    </item>
    <item>
      <title>[MIL] 프론트엔드 데브코스 5기 한 달 회고 - 4회차</title>
      <link>https://developer-gaeppu.tistory.com/160</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2EJvK/btsDTu9uLeM/ZkVKxvbfrx07TQ36wIqiG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2EJvK/btsDTu9uLeM/ZkVKxvbfrx07TQ36wIqiG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2EJvK/btsDTu9uLeM/ZkVKxvbfrx07TQ36wIqiG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2EJvK%2FbtsDTu9uLeM%2FZkVKxvbfrx07TQ36wIqiG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;402&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;한 달 동안 &lt;b&gt;소셜 네트워크 서비스 구현&lt;/b&gt;&amp;nbsp;&lt;b&gt;프로젝트&lt;/b&gt;를 진행했다. 그 한 달간의 회고이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qVb9I/btsDLKTldnm/6z2Vzha4a8djHMfzd9m3EK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qVb9I/btsDLKTldnm/6z2Vzha4a8djHMfzd9m3EK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qVb9I/btsDLKTldnm/6z2Vzha4a8djHMfzd9m3EK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqVb9I%2FbtsDLKTldnm%2F6z2Vzha4a8djHMfzd9m3EK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;247&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;377&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt; 긴장되는 첫 프로젝트&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;웹 프론트엔드 개발 프로젝트는 처음이어서 긴장을 많이 했다. 프로젝트 기간 이전에 온라인 강의를 들으면서도 계속 프로젝트를 걱정했던 거 같다. 그러던 중에 팀에서 일찍이 팀프로젝트를 준비하는 것이 어떠냐는 의견이 나왔고 공식 일정보다 일찍 프로젝트를 시작하게 되었다. 걱정이 있었던 지라 일찍 시작하는 게 심적으로 부담도 적고 여유롭게 마칠 수 있을 것 같아 너무 좋았다. 본격적 팀프로젝트에 앞서 팀 역할로 서기를 맞게 되었다. 초반에는 팀원분들이 말씀하시는 중간에 끊기가 힘들어 놓치기도 했고 익숙하지 않은 개발 용어에 당황할 때도 있었다. 점점 시간이 지나면서 나름의 정리하는 방법도 생기고 팀원분들께 편하게 말하게 되면서 차근차근 서기로서의 역할에 집중할 수 있었던 거 같다. (뭐든 하면 되더라...)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt; 프로젝트 목표&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로젝트를 하며 내 목표는 개발 기술, 커뮤니케이션 능력 향상도 있지만 속으로 생각한 가장 큰 목표는 1인분은 제대로 하자였다. 스스로 타입스크립트, 상태관리 등 기술적인 부분의 부족함을 알고 있다보니 1인분도 못할까 걱정을 많이 했다. 그래서 최대한 내가 맡은 역할에는 최선을 다하려고 노력했다. 맡은 역할을 잘하고 싶어서 자료도 많이 보고 시간도 많이 투자한 거 같다. 확실히 처음 하는 영역이 많아 오래 걸렸다. 초반에는 컴포넌트 하나 만드는데도 정말 오래 걸렸던 거 같다. 프로젝트를 진행하면서 점점 감을 잡을 수 있었다. 또 내가 맡은 역할을 완수하는 데 있어 내 실력에 부족함이 있다면 팀원분들께 빠르게 질문하고 피드백받는 것이 중요하다는 것을 깨달았다. 팀원분들의 시간을 뺐지 않기 위해 스스로 고민하는 시간이 길어질 때도 있었는데 고민하는 시간을 구체적으로 설정하고 그 이후에도 해결이 어려울 시에 바로 도움을 요청하는 게 팀에게 더 이로운 선택이라는 것을 깨달았다. 함께 해결하는 과정에서 시간적인 효율성도 높일 수 있고 또 혼자 할 때보다 의논하는 과정에서 더 많은 것을 얻을 수 있다는 것을 몸소 느꼈다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt; 반성하는 것&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로젝트를 하며 내가 맡은 일에 최선을 다해야한다는 것에 집중하다 보니 팀 전체적인 흐름에 많은 도움이 되지 못한 거 같아 반성하게 되었다. 내 주 업무는 게시판 작성과 수정하기 페이지 구현이었다.&amp;nbsp; 내 업무에 집중해서 막 하다가 정신을 차리고 보면 팀 개발진도가 어느새 많이 나가 있는 듯한 느낌이었다. 내 업무를 보는 동안 다른 팀원분들도 열일하시니 당연한 건데 뭔가 모르는 코드도 있고 하다 보니 팀 전체적인 흐름을 못 따라잡고 있는 듯한 느낌도 있었고 기술적인 의견이나 코드 리뷰에 더 깊게 참여하지 못한 거 같아 반성하게 되었다. 전체적인 팀 흐름에 도움이 되는 사람이 되고 싶은데 아직 부족해서 여유가 없었던 거 같다. 나중에는 나도 팀에 기술 스택 선택 등에서 건설적인 의견도 내고 팀의 흐름도 이끌 수 있는 개발자로 성장하고 싶다는 생각을 했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로젝트 초반에 시간내에 내 업무를 빠르게 처리해야 한다는 생각에 &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #333333; text-align: start;&quot;&gt;chat GPT를&lt;/span&gt; 많이 활용했다. 극초엔 거의 남용이었던 거 같다. 정신 차리고 보니 비효율적인 코드도 있고 팀 컨벤션과는 거리가 먼 코드도 있었다. 챗봇을 활용하더라도 내가 구현하고자 하는 코드에 대한 고민을 충분히 한 후 그에 맞는 명령을 내리고&amp;nbsp; 제공된 코드의 로직을 명확하게 이해하고 고민하는 과정이 필수적이었다. 또한 내가 생각한 코드와 비교하며 적정선에서 활용하는 것이 더 좋은 퀄리티의 코드를 만들어 낸다는 것을 깨달았다. &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #374151; text-align: start;&quot;&gt;시간이 흐를수록&lt;/span&gt;&amp;nbsp; 플랫폼의 의존도를 낮추고&amp;nbsp;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #374151; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;스스로 고민하고 문제를 해결하려고&lt;/span&gt; 하게 된 거 같다. &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #374151; text-align: start;&quot;&gt;개발에 유용한 기술이 계속 늘어나는 만큼&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #374151; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;스스로 사고하고 문제를 해결하는 능력이&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #374151; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #374151; text-align: start;&quot;&gt;개발자에게 정말 중요한 역량이 된 거 같다.&amp;nbsp;&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #374151; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #374151; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt; 완벽은 없다.&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #374151; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로젝트를 진행하면서 개발에는 완벽도 끝도 없다는 것을 깨달았다. 어느정도 구현했다 싶다가도 오류가 생긴다. 오류 하나를 고치면 또 다른 오류가 생겨나기도 하고 발견되기도 한다. 또 수정하고 싶은 부분도 계속해서 생긴다. 정말 완벽은 없다는 것을 느꼈다. 프로젝트를 일찍 시작한 덕분에 많은 사용자와 멘토님께 피드백을 받을 수 있는 기회가 많았다. 피드백을 계속 받으면서 크게 4차례에 걸친 리팩토링을 진행했다. 그 외에 자잘한 수정도 많았다. 초반에는 무언가 잘못되게 발견되면 덜컥 쫄았었다. 그러나 계속해서 수정하고 리팩토링 하는 과정에서 알게 된 게 있다. 결국엔 어떤 방식으로든 해결힐 수 있다는 것이다. 든든한 팀원 분들이 있고 참고할 수 있는 문서와 블로그도 많다 보니 차근차근 해결해갈 수 있었다. 시간적인 여유가 있어서 그랬을지도 모른다. 나중에는 새로운 버그가 생기더라도 초반보단 두려움이 조금 사라졌던 거 같다. 결국에는 오류가 해결되는 게 개발의 매력인 거 같기도...?&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #374151; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #374151; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt; 프로젝트 완료&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1743&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2W1jk/btsDNGC5YcD/YbrUi7w4Sbk8Ux5dzv96Mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2W1jk/btsDNGC5YcD/YbrUi7w4Sbk8Ux5dzv96Mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2W1jk/btsDNGC5YcD/YbrUi7w4Sbk8Ux5dzv96Mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2W1jk%2FbtsDNGC5YcD%2FYbrUi7w4Sbk8Ux5dzv96Mk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;247&quot; data-origin-width=&quot;1743&quot; data-origin-height=&quot;862&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #374151; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;한달동안 팀원분들과 열일한 만큼 성공적으로 프로젝트를 마칠 수 있었다. 아직 미흡한 부분이 있지만 목표로 했던 기능을 구현했다는 것에서 성취감을 많이 느끼게 되는 거 같다. 더 세세한 프로젝트 과정은 따로 블로그에 작성해야 할 것 같다. 프로젝트를 마치면서 내가 제대로 한 게 맞나? 하는 의심도 있긴 하지만 결과적으론 성취감과 기쁜 점이 더 많은 거 같다. 팀원분들께 감사하고 내가 맡은 일은 책임을 다해 해냈으니 감사하다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;역시 하루종일 노는 거 보다 사람들과 바쁘고 생산적이게 사는 게 힘들지만 더 행복한 거 같다. 다음 프로젝트에는 더 발전된 모습으로 임하고 싶다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #374151; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #374151; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #374151; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #374151; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드/프론트엔드 데브코스 TIL</category>
      <category>데브코스</category>
      <category>프로그래머스</category>
      <author>hyeon200</author>
      <guid isPermaLink="true">https://developer-gaeppu.tistory.com/160</guid>
      <comments>https://developer-gaeppu.tistory.com/160#entry160comment</comments>
      <pubDate>Tue, 23 Jan 2024 16:21:51 +0900</pubDate>
    </item>
    <item>
      <title>[MIL] 프론트엔드 데브코스 5기 한 달 회고 - 3회차</title>
      <link>https://developer-gaeppu.tistory.com/145</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wtQT3/btsCAiJjGij/DqFdLkteQWhjH0rJRYSex1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wtQT3/btsCAiJjGij/DqFdLkteQWhjH0rJRYSex1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wtQT3/btsCAiJjGij/DqFdLkteQWhjH0rJRYSex1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwtQT3%2FbtsCAiJjGij%2FDqFdLkteQWhjH0rJRYSex1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;402&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;✔️이번 단위기간에 공부한 것들&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;vue&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;react&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;✔️과제 회고&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;영화검색페이지(Vue)&lt;/b&gt;&lt;/span&gt;&amp;nbsp;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;911&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IKXtJ/btsCBLEwUJh/ZuIyAprcS5WvFdhx8wk2s0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IKXtJ/btsCBLEwUJh/ZuIyAprcS5WvFdhx8wk2s0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IKXtJ/btsCBLEwUJh/ZuIyAprcS5WvFdhx8wk2s0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/IKXtJ/btsCBLEwUJh/ZuIyAprcS5WvFdhx8wk2s0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;536&quot; height=&quot;254&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;911&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: left;&quot; href=&quot;http://movie-searching-site-vue-ypl5.vercel.app&quot;&gt;배포링크&lt;/a&gt;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: left;&quot;&gt;타입스크립트와 Vue.js을 활용하여 영화 검색 사이트를 만들었다.&amp;nbsp; 라우터 설정과 관리가 중요했던 과제인 거 같다.&amp;nbsp; 라우터 관련 코드리뷰를 많이 받으면서 route구조를 더 깔끔히 하고 효율적인 렌더링 방식으로 리팩토링할 수 있었다. 또 이번에 &lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;Tailwind CSS&lt;/span&gt; 도 사용하면서 새로운 스타일 툴도 사용해 본 좋은 경험이었다. 코드가 너무 길어지고 복잡해지는 단점이 있지만 따로 className을 설정하지 않아도 되어 장단점이 명확한 라이브러리 인 거 같다. 또 이번에 코드리뷰를 진행하면서 meta태그도 알게 되었다. 이후 과제나 프로젝트에서 meta태그를 적극적으로 활용해봐야겠다.&amp;nbsp;&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Tooltip 컴포넌트 구현 (React)&lt;/b&gt;&lt;/span&gt;&amp;nbsp;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p8tOD/btsCy6W0csH/cTlfSxW72hi6S7qkj4X8hK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p8tOD/btsCy6W0csH/cTlfSxW72hi6S7qkj4X8hK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p8tOD/btsCy6W0csH/cTlfSxW72hi6S7qkj4X8hK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/p8tOD/btsCy6W0csH/cTlfSxW72hi6S7qkj4X8hK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;261&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;React, TypeScript를 이용하여 Tooltip 컴포넌트를 구현했다. 스타일링 라이브러리는 강의를 통해 배운 emotion을 활용했다. react가 처음이어서 과정이 꽤 오래걸렸던 거 같다. Tooltip을 구현하기 위한 컴포넌트와 훅을 직접 만들어보니까 react에서 컴포넌트의 중요성을 더 이해할 수 있었던 거 같다. &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;컴포넌트 재사용에 대해서도 고민할 수 있는 과제였다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;✔️한달&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;후 성장과&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;&amp;nbsp;느낀 점&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: left; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이번 한 달동안은 vue.js와 react 강의를 들으며 학습을 했다. 프레임워크. 라이브러리를 공부하면서 컴포넌트에 대해서도 많이 생각하게 된 거 같다. 또 이전에는 잘 와닿지 않았던 디자인시스템과 같은 개념들도 조금씩 이해가 되기 시작했다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로그래머스 데브코스에 참여하면서 프론트엔드 개발 용어와 개념에 점점 익숙해지는 느낌이 들어 다행이라는 생각이 든다. 또 한편으로는 다양한 걸 배우지만 기본이 잘 쌓아지고 있는 것일까?라는 의문이 들어서 반성도 하게 되는 거 같다. 부족한 부분은 넘기지말고 스스로 더 공부해야할 것 같다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞으로 남은 프로그래머스 데브코스 기간동안 더 많은 경험을 하면서 바로바로 이해하고 커뮤니케이션할 수&amp;nbsp; 있는 사람이 되고 싶다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; &lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;팀 프로젝트가 본격적으로 시작되어 정신이 없지만 &lt;/span&gt;열심히 공부하고 익혀서 나의 역할을 정말 잘 해내고 싶다. 또 잘 소통하면서 팀원들과 프로젝트를 성공적으로 마무리하는 것이 앞으로의 목표이다. 화이팅!! &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래머스</category>
      <author>hyeon200</author>
      <guid isPermaLink="true">https://developer-gaeppu.tistory.com/145</guid>
      <comments>https://developer-gaeppu.tistory.com/145#entry145comment</comments>
      <pubDate>Mon, 25 Dec 2023 02:04:24 +0900</pubDate>
    </item>
  </channel>
</rss>