<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>내가 보려고 만든 블로그</title>
    <link>https://become-a-developer.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 10 Apr 2026 22:53:17 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>나나나나나나나ㅏ나난ㄴ나ㅏ나나</managingEditor>
    <image>
      <title>내가 보려고 만든 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/3200656/attach/917c425de7654307b4764f8c4f09ec4f</url>
      <link>https://become-a-developer.tistory.com</link>
    </image>
    <item>
      <title>Flutter Flavor 완전 정복: 개발/운영 환경 완벽 분리 가이드 (feat. .env, Android &amp;amp; iOS)</title>
      <link>https://become-a-developer.tistory.com/142</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;정----말 오랜만에 Flutter로 사이드 프로젝트를 시작했습니다. 한동안 손 놓고 있던 사이에 Flutter 버전은 2.x대에서 3.x대로 훌쩍 업데이트되었고, 그러면서 바뀐 점들도 꽤 있더라고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행하며 알게 된 몇 가지를 정리해 보려고 합니다. 그 첫 번째 타자가 바로 &lt;b&gt;Flavor&lt;/b&gt;! 그럼 시작해 볼까요?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Flavor란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행하거나 출시하다 보면 하나의 소스 코드로 여러 환경에 맞춰 앱을 빌드해야 할 때가 많습니다. 개발, 스테이징, 운영 등등.. 각 환경은 서로 다른 API 주소나 앱 이름, 아이콘을 가져와야 하죠. 만약 이런 설정을 수동으로 변경한다면 개발 서버를 바라봐야 할 앱이 운영 서버를 본다거나 하는 크리티컬한 실수를 피하기 쉽지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter의 &lt;b&gt;Flavor&lt;/b&gt;는 이런 문제를 해결하기 위한 공식적인 기능입니다. 다음에 또 허둥댈 저를 위해, 처음부터 끝까지 정리해 보겠습니다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계: 환경별 main 파일 및 설정 파일 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 Dart 코드 단에서 각 환경을 구분할 수 있는 파일을 만들어 줍니다.&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;1. 환경별 진입점, main_ 파일 만들기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lib 폴더 안에 각 환경의 시작점이 될 파일을 생성해 줍니다. 이 파일의 역할은 앱이 어떤 Flavor로 실행되어야 하는지 알려주는 것입니다.&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;lib/main_dev.dart&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'flavors.dart';
import 'main.dart'; // 모든 환경이 공통으로 사용할 앱 시작 로직

void main() {
  F.appFlavor = Flavor.dev; // 앱 시작 전에 'dev' Flavor라고 알려주기
  startApp();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(똑같이 main_production.dart 파일도 만들고 F.appFlavor = Flavor.production;으로 설정해 줍니다.)&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;b&gt;2. 설정값 관리자, flavors.dart 만들기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경별로 달라질 앱 제목, API 주소 등의 값을 이곳에서 중앙 관리합니다.&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;lib/flavors.dart&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;enum Flavor { dev, production }

class F {
  static late final Flavor appFlavor;

  static String get title {
    switch (appFlavor) {
      case Flavor.dev:
        return '내 앱 (개발)';
      case Flavor.production:
        return '내 앱';
    }
  }
  // ... API 주소 등 다른 설정도 여기에 추가!
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계: 민감 정보는 .env로!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 키처럼 Git에 올리면 절대 안 되는 민감 정보는 .env 파일로 따로 빼서 관리하는 게 안전하겠죠?&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;1. 패키지 설치 및 파일 생성&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;flutter pub add flutter_dotenv
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 최상단에 .env 파일을 만들고 키를 넣어줍니다.&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;.env&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;HASURA_ADMIN_SECRET=&quot;your_secret_api_key_here&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;b&gt;2. .gitignore에 .env 파일 추가하기&lt;/b&gt; 가장 중요한 단계! .gitignore 파일 맨 아래에 .env*를 추가해서 Git이 이 파일을 추적하지 못하게 막아줍니다.&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;b&gt;3. 공통 main.dart에서 .env 파일 로드하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Flavor가 공통으로 사용할 startApp() 함수에서 앱이 실행되기 전에 .env 파일을 로드하도록 코드를 추가합니다.&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;lib/main.dar&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'app.dart'; // MaterialApp 위젯이 있는 파일

Future&amp;lt;void&amp;gt; startApp() async {
  // .env 파일을 찾아 변수를 메모리에 로드합니다.
  await dotenv.load(fileName: &quot;.env&quot;);
  
  // 이제 앱을 실행합니다.
  runApp(const MyApp());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3단계: 네이티브(Android/iOS) 설정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart 코드 설정이 끝났다고 방심하면 안 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 앱은 결국 네이티브 앱으로 빌드되기에, Android와 iOS에게도 우리가 만든 Flavor를 알려줘야 합니다.&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;b&gt;LateError (LateInitializationError: Field 'appFlavor' has not been initialized.)&lt;/b&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;b&gt;1. Android 설정 (build.gradle.kts)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;android/app/build.gradle.kts 파일을 열고 아래 내용을 추가해 주세요.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;android {
    ...
    flavorDimensions += &quot;app&quot;

    productFlors {
        create(&quot;dev&quot;) {
            dimension = &quot;app&quot;
            // 앱 패키지명에 .dev를 붙여 운영 버전과 같이 설치할 수 있게 함
            applicationIdSuffix = &quot;.dev&quot; 
            // 안드로이드 앱 이름을 변경
            resValue(&quot;string&quot;, &quot;app_name&quot;, &quot;내 앱 (개발)&quot;)
        }
        create(&quot;production&quot;) {
            dimension = &quot;app&quot;
            resValue(&quot;string&quot;, &quot;app_name&quot;, &quot;내 앱&quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;b&gt;2. iOS 설정 (Xcode Schemes)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS는 Xcode에서 Scheme을 이용해 Flavor를 설정합니다. 조금 복잡하지만 차근차근 따라 해보세요.&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;Xcode 실행&lt;/b&gt;: 터미널에서 open ios/Runner.xcworkspace 명령어로 Xcode를 엽니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Configuration 복제&lt;/b&gt;: Project 'Runner' &amp;gt; Info &amp;gt; Configurations에서 Debug와 Release를 각각 복제하여 Debug-dev, Release-dev를 만듭니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Scheme 복제&lt;/b&gt;: 상단 메뉴 Product &amp;gt; Scheme &amp;gt; Manage Schemes...에서 Runner를 복제하여 Runner-dev를 만듭니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Scheme과 Configuration 연결&lt;/b&gt;: Runner-dev의 Edit Scheme... &amp;gt; Build 탭에서 각 단계의 Configuration을 위에서 만든 Debug-dev, Release-dev로 바꿔줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;앱 이름, 번들 ID 설정&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Build Settings에서 + 버튼을 눌러 User-Defined Setting을 추가하고 APP_NAME과 BUNDLE_ID_SUFFIX 변수를 만듭니다.&lt;/li&gt;
&lt;li&gt;Debug-dev 환경에 맞춰 APP_NAME은 내 앱 (개발), BUNDLE_ID_SUFFIX는 .dev로 값을 설정합니다.&lt;/li&gt;
&lt;li&gt;Info.plist 파일을 열어 Bundle display name의 값을 $(APP_NAME)으로, Bundle identifier의 값을 $(PRODUCT_BUNDLE_IDENTIFIER)$(BUNDLE_ID_SUFFIX)로 수정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;## 4단계: VS Code에서 편하게 실행하기 (launch.json)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, 매번 긴 명령어를 치는 대신 F5 키로 편하게 디버깅하기 위해 VS Code를 설정해 줍니다.&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;.vscode/launch.json&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
    &quot;version&quot;: &quot;0.2.0&quot;,
    &quot;configurations&quot;: [
        {
            &quot;name&quot;: &quot;  DEV&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;type&quot;: &quot;dart&quot;,
            &quot;program&quot;: &quot;lib/main_dev.dart&quot;,
            &quot;args&quot;: [&quot;--flavor&quot;, &quot;dev&quot;]
        },
        {
            &quot;name&quot;: &quot;✅ PRODUCTION&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;type&quot;: &quot;dart&quot;,
            &quot;program&quot;: &quot;lib/main_production.dart&quot;,
            &quot;args&quot;: [&quot;--flavor&quot;, &quot;production&quot;]
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;VS Code의 'Run and Debug' 탭에서 원하는 Flavor를 선택하고 실행하면 끝!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;</description>
      <category>FRAMEWORK/FLUTTER</category>
      <category>Flutter</category>
      <category>flutter flavors</category>
      <category>LateError (LateInitializationError: Field 'appFlavor' has not been initialized.)</category>
      <category>개발환경</category>
      <author>나나나나나나나ㅏ나난ㄴ나ㅏ나나</author>
      <guid isPermaLink="true">https://become-a-developer.tistory.com/142</guid>
      <comments>https://become-a-developer.tistory.com/142#entry142comment</comments>
      <pubDate>Wed, 3 Sep 2025 19:43:29 +0900</pubDate>
    </item>
    <item>
      <title>SSR 과 CSR, 서버사이드 렌더링과 클라이언트 사이드 렌더링 그리고 SPA</title>
      <link>https://become-a-developer.tistory.com/141</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;어렴풋이 알고만 있던 SSR과 CSR, 그리고 SPA 알고는 있지만, 이걸 정의해보라고 시키면 말하기는 어려운 친구들...&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;h2 id=&quot;ssrserver-side-rendering&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 id=&quot;ssr-설명&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;SSR (Server Side Rendering) 란?&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이름 그대로 &lt;b&gt;서버에서 페이지를 모두 렌더링해서&lt;/b&gt; 브라우저에 전달하는 방식이다. 브라우저는 렌더링이 거의 끝난 HTML 문서를 전달받기 때문에, 초기 화면을 매우 빠르게 표시할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SSR의 동작 과정&lt;/b&gt;&lt;/h4&gt;
&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;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bT7PJG/btsPQCakloa/SRBUzrWrgunlBYHah4R4ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bT7PJG/btsPQCakloa/SRBUzrWrgunlBYHah4R4ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bT7PJG/btsPQCakloa/SRBUzrWrgunlBYHah4R4ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbT7PJG%2FbtsPQCakloa%2FSRBUzrWrgunlBYHah4R4ek%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;1280&quot; height=&quot;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&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;SSR의 동작 순서를 간단히 나타내면 이렇다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 브라우저를 통해 웹사이트에 접속을 요청한다.&lt;/li&gt;
&lt;li&gt;서버는 요청을 받고, 페이지를 렌더링할 준비를 마친 &lt;b&gt;완전한 형태의 HTML 파일&lt;/b&gt;을 생성한다.&lt;/li&gt;
&lt;li&gt;브라우저는 서버로부터 이 HTML 파일을 받아 즉시 화면에 표시한다. 사용자는 콘텐츠를 바로 볼 수 있지만, 아직 상호작용에 필요한 JavaScript가 적용되지 않아 버튼 클릭 등은 동작하지 않는다. (이 시점을 &lt;b&gt;TTV, Time To View&lt;/b&gt;라고 부른다)&lt;/li&gt;
&lt;li&gt;브라우저는 이어서 페이지 상호작용에 필요한 JavaScript 파일을 다운로드한다.&lt;/li&gt;
&lt;li&gt;다운로드한 JavaScript를 HTML 문서(DOM)에 적용하는 &lt;b&gt;'Hydration(하이드레이션)'&lt;/b&gt; 과정을 거친다.&lt;/li&gt;
&lt;li&gt;Hydration이 완료되면 비로소 페이지의 모든 기능이 활성화되고, 사용자는 정상적으로 상호작용할 수 있다. (이 시점이 바로 &lt;b&gt;TTI, Time To Interact&lt;/b&gt;이다)&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;1280&quot; data-origin-height=&quot;913&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BfTEC/btsPO723RBo/vSqS77FjrW7AM4uwD3lOf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BfTEC/btsPO723RBo/vSqS77FjrW7AM4uwD3lOf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BfTEC/btsPO723RBo/vSqS77FjrW7AM4uwD3lOf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBfTEC%2FbtsPO723RBo%2FvSqS77FjrW7AM4uwD3lOf1%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;1280&quot; height=&quot;913&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;조금 더 간소화 해서 보면&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서버가 렌더링된 HTML 파일을 보내고 브라우저가 받음&lt;/li&gt;
&lt;li&gt;브라우저가 페이지를 화면에 띄워주고 JS 파일을 다운 받는다&lt;/li&gt;
&lt;li&gt;브라우저가 프레임워크 (React)를 실행한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;페이지 작동!&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;정리하자면, 서버 사이드 렌더링은 사용자의 요청이 있을 때마다 서버에서 페이지를 새로 만들어 제공하는 방식이다. 즉, 화면을 그리는(렌더링) 주체가 서버! &lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #000000;&quot;&gt;과거의 웹은 대부분 이 방식을 사용했지만, 웹에서 제공되는 정보의 양이 많아지면서 문제가 발생했는데, 페이지를 이동할 때마다 전체 페이지를 새로고침하므로 화면 깜빡임(flicker)이 발생하고, 서버에 계속해서 부담을 주는 성능 저하의 원인이 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #666666; text-align: start;&quot; 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;style2&quot; /&gt;
&lt;p style=&quot;color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SSR의 장점과 단점&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;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;검색엔진 최적화(SEO) 유리&lt;/b&gt;: 서버가 콘텐츠가 모두 채워진 HTML을 전달하므로 검색엔진 크롤러가 사이트 정보를 수집하기 용이하다.&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;b&gt;느린 페이지 전환&lt;/b&gt;: 다른 페이지로 이동할 때마다 서버에 새로운 페이지를 요청하고 받아오는 과정을 반복하므로 화면 전체가 새로고침되어 깜빡인다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 부하&lt;/b&gt;: 사용자의 요청이 있을 때마다 서버가 페이지를 새로 렌더링해야 하므로, 트래픽이 많은 서비스의 경우 서버에 부하가 갈 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TTV와 TTI의 간극&lt;/b&gt;: 사용자가 화면을 보고 나서 실제로 조작할 수 있을 때까지 지연 시간이 존재한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 id=&quot;장단점&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;SPA (Single Page Application) 란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SPA는 Single Page Application의 약자로, &lt;b&gt;하나의 HTML 페이지만으로 전체 서비스를 운영&lt;/b&gt;하는 애플리케이션을 뜻한다. 최초 접속 시에만 전체 페이지를 로드하고, 그 이후에는 사용자의 행동에 따라 필요한 데이터만 서버에서 받아와 &lt;b&gt;화면의 일부만 동적으로 교체&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식 덕분에 페이지 이동 시 SSR처럼 화면 전체가 깜빡이며 새로고침되지 않고, 마치 데스크톱 앱처럼 부드러운 사용자 경험을 제공할 수 있다. 이 SPA를 구현하는 대표적인 렌더링 방식이 바로 다음에 설명할 CSR이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 id=&quot;csr-client-side-rendering&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;CSR (Client Side Rendering)&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SSR과 반대로 렌더링의 책임이 &lt;b&gt;클라이언트(브라우저)에게&lt;/b&gt; 있다. 서버는 거의 텅 빈 HTML 뼈대와 렌더링에 필요한 JavaScript 파일만 보내주고, 브라우저가 이를 받아 동적으로 페이지를 그려나간다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;CSR의 동작 과정&lt;/b&gt;&lt;/h4&gt;
&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;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dchKvw/btsPRz42CAa/qloDuDpDlAlF4XA5HkbRT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dchKvw/btsPRz42CAa/qloDuDpDlAlF4XA5HkbRT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dchKvw/btsPRz42CAa/qloDuDpDlAlF4XA5HkbRT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdchKvw%2FbtsPRz42CAa%2FqloDuDpDlAlF4XA5HkbRT1%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;1280&quot; height=&quot;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 웹사이트 접속을 요청한다.&lt;/li&gt;
&lt;li&gt;서버는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;내용이 거의 없는 최소한의 HTML 파일&lt;/b&gt;과 화면 구성에 필요한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;JavaScript 파일 링크&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;다운로드가 완료된 JavaScript 파일이 실행되며 화면을 동적으로 구성한다.&lt;/li&gt;
&lt;li&gt;페이지에 필요한 데이터가 있다면, JavaScript가 서버에 API를 호출해 데이터를 요청한다.&lt;/li&gt;
&lt;li&gt;서버로부터 데이터를 수신하면, 그제야 빈 페이지의 적절한 위치에 데이터를 채워 넣어 사용자에게 완전한 화면을 보여준다&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;1280&quot; data-origin-height=&quot;903&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o5kFL/btsPP0vWZ8B/GtamNxJmoueuhgwlFYgxVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o5kFL/btsPP0vWZ8B/GtamNxJmoueuhgwlFYgxVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o5kFL/btsPP0vWZ8B/GtamNxJmoueuhgwlFYgxVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo5kFL%2FbtsPP0vWZ8B%2FGtamNxJmoueuhgwlFYgxVk%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;1280&quot; height=&quot;903&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;903&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR 과 달리 CSR은 화면이 띄워지는 타이밍이 확실히 늦다. 브라우저가 HTML 파일, JS 파일 까지 모두 다 읽은 뒤 프레임워크까지 실행하고 난 뒤에 페이지가 노출되고, 상호작용이 가능해진다.&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 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정리하자면 클라이언트 사이드 렌더링은 클라이언트인 브라우저가 렌더링을 도맡아 처리하는 방식이다. 서버에서 필요한 데이터를 한 번에 받아오고 받은 데이터를 브라우저가 주체가 되어 그린다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;CSR은 SPA 트랜드와 CPU 성능 상승 +JS 표준화(리액트, 뷰, 앵귤러 등 프레임워크의 발전)와 함께 본격적으로 시작되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트사이드렌더링은 서버에서 데이터를 받아 모두 읽고 다시 화면에 전체 페이지를 바꿔서 보여주는 것보다 훨씬 빠르게 화면을 그릴 수 있는 것이다. 이는 성능적으로도 페이지를 새로 렌더링하지 않아 서버자원을 덜 사용하고, 새로고침이 발생하지 않기 때문에 화면이 바뀔때 화면 깜빡임도 없어 우수한 사용자 경험을 제공한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; 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;style2&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;CSR의 장점과 단점&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;뛰어난 사용자 경험(UX)&lt;/b&gt;: 초기 로딩 이후에는 페이지 전체를 새로고침할 필요 없이 필요한 부분만 빠르게 업데이트하므로, 매우 부드러운 화면 전환이 가능하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 부하 감소&lt;/b&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;b&gt;느린 초기 로딩 속도&lt;/b&gt;: 모든 스크립트 파일이 다운로드되고 실행될 때까지 사용자는 기다려야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검색엔진 최적화(SEO) 불리&lt;/b&gt;: 대부분의 검색엔진 크롤러는 텅 빈 HTML 문서를 보고 내용을 수집하지 못하는 경우가 많다. (최근 구글 등은 JS를 실행시켜주기도 하지만, 여전히 SEO에는 보완이 필요하다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 id=&quot;두-방식의-차이&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;그래서 뭐가 다른데? SSR VS CSR 한눈에 보기&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 108px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;SSR&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;CSR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;초기 로딩 속도&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;빠름&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;느림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;페이지 전환 속도&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;느림 (전체 새로고침)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;빠름 (부분 업데이트)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;SEO&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;유리&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;불리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;서버 부하&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;높음&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;사용자 경험&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;페이지 이동 시 깜빡임 발생&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 18px; text-align: center;&quot;&gt;부드럽고 쾌적함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; 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;h2 id=&quot;결론&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SSR과 CSR은 어느 하나가 무조건 좋은 '은 탄환(Silver Bullet)'이 아니다. 각자 명확한 장단점을 가지고 있어, 서비스의 성격에 따라 적절한 방식을 선택해야 한다.&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;SSR이 유리한 경우&lt;/b&gt;: SEO가 매우 중요하거나, 콘텐츠 중심의 정적인 페이지(뉴스 기사, 블로그 등), 혹은 사용자와의 상호작용이 적은 웹사이트에 적합하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CSR이 유리한 경우&lt;/b&gt;: 복잡한 상호작용과 빠른 인터랙션이 중요한 서비스(대시보드, 관리자 페이지 등), 혹은 SEO의 중요도가 낮은 서비스에 적합하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 Next.js(React 기반)나 Nuxt.js(Vue 기반) 같은 프레임워크가 등장하며 이 문제를 해결하고 있다. 이런 프레임워크들은 첫 페이지 로딩은 SSR 방식으로 처리해 SEO와 초기 로딩 속도를 잡고, 이후의 상호작용은 CSR 방식으로 처리해 부드러운 사용자 경험을 제공하는 &lt;b&gt;하이브리드 방식&lt;/b&gt;을 채택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 나아가 빌드 시점에 페이지를 미리 만들어두는 SSG(정적 사이트 생성)나, 필요할 때만 페이지를 다시 렌더링하는 ISR(점진적 정적 재생성) 같은 고급 전략도 등장했다. (공부할게 왜이렇게 많은건지,, 이것도 정리한번 하는게 좋을거같다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 중요한 것은 각 렌더링 방식의 원리를 정확히 이해하고, 만들고자 하는 서비스의 성격에 맞는 최적의 방식을 선택하고 조합하는 능력이다.&lt;/p&gt;</description>
      <category>CSR</category>
      <category>SPA</category>
      <category>SSR</category>
      <category>기술면접</category>
      <category>웹개발</category>
      <category>웹선능</category>
      <category>프론트엔드</category>
      <author>나나나나나나나ㅏ나난ㄴ나ㅏ나나</author>
      <guid isPermaLink="true">https://become-a-developer.tistory.com/141</guid>
      <comments>https://become-a-developer.tistory.com/141#entry141comment</comments>
      <pubDate>Tue, 12 Aug 2025 19:44:09 +0900</pubDate>
    </item>
    <item>
      <title>Next.js vs Nuxt.js ?</title>
      <link>https://become-a-developer.tistory.com/140</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 웹개발 시장을 보면 서버사이드 렌더링 (SSR) 과 정적 사이트 생성 (SSG)는 필수적인 기술이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 관련하여 React와 Vue.js 를 기반으로 한 대표 프레임워크인 Next.js 와 Nuxt.js 를 정리해보자.&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;현대 웹 개발에서 서버 사이드 렌더링(SSR)과 정적 사이트 생성(SSG)은 더 이상 선택이 아닌 필수 기술로 자리 잡았다. 뛰어난 사용자 경험(UX)과 검색 엔진 최적화(SEO)를 모두 잡기 위해 많은 개발자가 React와 Vue.js 기반의 대표 프레임워크인 &lt;b&gt;Next.js&lt;/b&gt;와 &lt;b&gt;Nuxt.js&lt;/b&gt;를 사용하고 있다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Next.js란?&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;297&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kXwZI/btsPMP9YeIN/aVjYE3RHvIU0XnMma4P4tK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kXwZI/btsPMP9YeIN/aVjYE3RHvIU0XnMma4P4tK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kXwZI/btsPMP9YeIN/aVjYE3RHvIU0XnMma4P4tK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkXwZI%2FbtsPMP9YeIN%2FaVjYE3RHvIU0XnMma4P4tK%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;297&quot; height=&quot;170&quot; data-origin-width=&quot;297&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js는 React 기반의 프레임워크로, &lt;b&gt;Vercel&lt;/b&gt;이 주도적으로 개발하고 있다. React의 방대한 생태계와 유연성을 그대로 활용하면서, &lt;b&gt;App Router&lt;/b&gt;와 &lt;b&gt;React Server Components(RSC)&lt;/b&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;b&gt;주요 특징:&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;b&gt;하이브리드 렌더링:&lt;/b&gt; SSR, SSG, ISR(점진적 정적 재생성) 뿐만 아니라, 컴포넌트 단위로 서버 렌더링과 클라이언트 렌더링을 조합할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;App Router:&lt;/b&gt; 폴더 구조가 곧 URL이 되는 직관적인 라우팅 시스템으로, layout.js, page.js 등 정해진 파일명을 통해 중첩된 레이아웃을 손쉽게 구현함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;React Server Components (RSC):&lt;/b&gt; 서버에서만 렌더링되는 컴포넌트로, 클라이언트의 자바스크립트 번들 크기를 획기적으로 줄여 초기 로딩 성능 극대화.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;풍부한 생태계:&lt;/b&gt; React의 인기를 바탕으로 검증된 라이브러리와 자료가 풍부하여 복잡하고 규모 있는 애플리케이션 개발에 유리함.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Nuxt.js란?&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnHBpH/btsPM1Cbwkz/yeqxDwWO1kYPfAzh2BbbK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnHBpH/btsPM1Cbwkz/yeqxDwWO1kYPfAzh2BbbK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnHBpH/btsPM1Cbwkz/yeqxDwWO1kYPfAzh2BbbK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnHBpH%2FbtsPM1Cbwkz%2FyeqxDwWO1kYPfAzh2BbbK1%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;276&quot; height=&quot;183&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nuxt.js는 Vue.js를 기반으로 한 프레임워크로, '컨벤션 중심(Convention over Configuration)' 철학을 바탕으로 직관적이고 생산성 높은 개발 경험을 제공한다.. 특히 &lt;b&gt;Nuxt 3&lt;/b&gt; 버전부터 도입된 &lt;b&gt;Nitro 서버 엔진&lt;/b&gt;과 &lt;b&gt;Vue 3 Composition 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;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;b&gt;Nitro 서버 엔진:&lt;/b&gt; 코드 분할, 서버리스 환경 지원, 하이브리드 렌더링 등 최신 기술을 내장하여 API 엔드포인트 생성부터 렌더링까지 전반적인 성능 크게 향상.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Auto-imports:&lt;/b&gt; 컴포넌트나 유틸리티 함수를 별도의 import 구문 없이 자동으로 불러와 코드의 간결함 유지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타입스크립트 완벽 지원:&lt;/b&gt; 강력한 타입 추론과 자동 완성 기능으로 안정적이고 예측 가능한 개발을 도움.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모듈 생태계:&lt;/b&gt; SEO, 이미지 최적화, 인증 등 자주 사용되는 기능들을 잘 만들어진 모듈을 통해 손쉽게 추가할 수 있어 빠른 속도로 개발을 할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;장단점 비교&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Next.js&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&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;높은 자유도와 확장성: React의 유연함과 RSC를 활용해 복잡한 대규모 아키텍처 설계 가능&lt;/li&gt;
&lt;li&gt;압도적인 생태계: 방대한 라이브러리와 커뮤니티, 풍부한 학습 자료&lt;/li&gt;
&lt;li&gt;점진적 채택 용이: 기존 React 프로젝트에 점진적으로 도입하기 용이함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점:&amp;nbsp;&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;다소 높은 학습 곡선: App Router, RSC 등 최신 개념에 대한 학습 필요&lt;/li&gt;
&lt;li&gt;복잡한 설정: 자유도가 높은 만큼 프로젝트가 복잡해지면 설정 관리가 까다로워질 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Nuxt.js&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점:&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;탁월한 개발자 경험(DX): Auto-imports, 규약 기반 설정 등 간결하고 빠른 개발에 초점&lt;/li&gt;
&lt;li&gt;강력한 내장 기능: Nitro 엔진 기반의 뛰어난 성능과 최적화된 모듈 생태계&lt;/li&gt;
&lt;li&gt;쉬운 학습 곡선: Vue.js의 직관성과 잘 정돈된 문서 덕분에 입문자에게 친화적&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;&lt;b&gt;단점&lt;/b&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;상대적으로 작은 생태계: React 생태계에 비해 특정 고급 기능 구현 시 선택지가 적을 수 있음&lt;/li&gt;
&lt;li&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주요 사용 사례 및 추천&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Next.js&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;팀이 React와 그 생태계에 대한 깊은 이해도를 가지고 있을 때&lt;/li&gt;
&lt;li&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;&lt;b&gt;Nuxt.js&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;팀이 Vue.js에 익숙하며, 높은 생산성을 중요하게 생각할 때&lt;/li&gt;
&lt;li&gt;뛰어난 개발자 경험(DX)과 잘 갖춰진 모듈 시스템을 선호할 때&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에는 '대규모는 Next.js, 중소규모는 Nuxt.js'라는 인식이 있었지만, Next.js 13+와 &lt;b&gt;Nuxt 3&lt;/b&gt;의 등장으로 이러한 구분은 많이 흐려진 상황이다. 두 프레임워크 모두 현대적인 웹 애플리케이션이 요구하는 대부분의 모두 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 기술 선택의 핵심은 &quot;어떤 프레임워크가 우리 팀의 개발 철학과 더 잘 맞는가?&quot;에 있습니다. React의 자유로운 생태계 속에서 최신 기술을 탐구하고 싶다면 &lt;b&gt;Next.js&lt;/b&gt;를, Vue.js의 직관적인 문법과 생산성을 바탕으로 빠르게 결과물을 만들고 싶다면 &lt;b&gt;Nuxt.js&lt;/b&gt;를 선택하여 프로젝트를 시작하면 될거같다!&lt;/p&gt;</description>
      <category>FRAMEWORK</category>
      <category>next</category>
      <category>nuxt</category>
      <author>나나나나나나나ㅏ나난ㄴ나ㅏ나나</author>
      <guid isPermaLink="true">https://become-a-developer.tistory.com/140</guid>
      <comments>https://become-a-developer.tistory.com/140#entry140comment</comments>
      <pubDate>Mon, 11 Aug 2025 16:47:30 +0900</pubDate>
    </item>
    <item>
      <title>vuejs input keyup.enter</title>
      <link>https://become-a-developer.tistory.com/139</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;vuejs 에서 input 내 키보드로 enter 쳤을때 함수 실행하기&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;pre id=&quot;code_1752659495141&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;v-on:keyup.enter=&quot;login()&quot;

@keyup.enter=&quot;login()&quot;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>FRAMEWORK/VUE</category>
      <category>enter</category>
      <category>key up enter</category>
      <category>keyup</category>
      <category>vuejs</category>
      <author>나나나나나나나ㅏ나난ㄴ나ㅏ나나</author>
      <guid isPermaLink="true">https://become-a-developer.tistory.com/139</guid>
      <comments>https://become-a-developer.tistory.com/139#entry139comment</comments>
      <pubDate>Wed, 16 Jul 2025 18:51:58 +0900</pubDate>
    </item>
    <item>
      <title>사용자 여정지도 (Journeymap)은 무엇일까?</title>
      <link>https://become-a-developer.tistory.com/137</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용자 여정지도란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;'여정지도'란 말 그대로 &lt;b&gt;사용자가 생활 맥락에서 제품/서비스와 만나는 지점을 살펴보고 기록&lt;/b&gt;해야 한다는 의미입니다. 인터뷰 결과물과 페르소나 활동으로 만든 가상의 사용자를 고려하여, 그 사용자가 실제 제품/서비스에서 겪는 경험을 시각화 하는것을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;일반적으로 여정지도를 작성하는 방버은 사용자가 제품 및 서비스를 접하기 전부터 사용중, 사용 후까지 행동들을 시간 순서대로 나열을 하고 그 행동들에 따르는 사용자의 생각과 감정을 채워서 정리해놓습니다. 경험을 행동, 감정, 생각으로 추출하고 그 속에서 이슈와 인사이트를 얻어내어 향후 제품 개선 및 새로운 개발을 위한 재료로 사용됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용자 여정지도 구조&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;상단: 특정 사용자 (User) + 시나리오 + 기대되는 행동 및 목표 (Expectations)&lt;br /&gt;중간: 구체적인 사용자의 행동 (Action) + 생각 (Thoughts) + 감정 (Feelings)&lt;br /&gt;하단: 문제점 (Painpoints) + 기회요소 (Opportunities)&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;/h4&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;경험하는 모든 과정에 대해 접점 (Touchpoint)를 확인합니다.&lt;/li&gt;
&lt;li&gt;작성 후에는 포스트잇이 많이 붙어있는 곳을 다시 보며 이야기를 나눕니다.&lt;/li&gt;
&lt;li&gt;여정지도를 사무실 벽에 붙여놓으세요. 앞으로 알게되는 사실들을 덧붙여 발전시킵니다.&lt;/li&gt;
&lt;/ul&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;사용자(User)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;사용자는 여정지도에서 특정 행동을 하는 대상으로 &lt;b&gt;페르소나(Persona)&lt;/b&gt;라고 부르기도 합니다. 수집한 데이터를 기반해서 사용자와 행동들을 선정합니다. 또한 필요시 저니맵을 두개 이상 제작할 수 있는데, 서비스를 사용하는 관계자가 두명 이상일 수도 있기 때문입니다. 그렇기 때문에 사용자를 선정할 때는 애매모호하고 여러 목표를 가진 페르소나가 아닌 &lt;b&gt;명확하고 뚜렷한 행동을 취할 수 있는 페르소나&lt;/b&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;시나리오 (Scenario) + 목표 (Expectations)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;사용자의 현재 상황을 묘사하고 행동을 통해 어떤 목적을 달성&lt;/b&gt;하려는지 적습니다. 현재 존재하는 서비스여도 되고 아직 디자인이 나오지 않은 서비스를 사용하는 상황이어도 좋습니다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;행동단계 (Journey Phase)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;행동단계는 사용자의 일련의 행동들이 전반적으로 어떤 과정인지 보여주는 상위단계입니다. 서비스의 성격에 따라 행동단계의 용어는 달라질 수 있는데 그 예를 들자면&lt;/p&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;/li&gt;
&lt;li&gt;B2B 서비스: 구입 - 채택 - 유지 - 확장 - 신뢰&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;행동, 생각, 감정 (Actions, Thoughts, Feelings)&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;행동(Actions)&lt;/b&gt;: 사용자가 제품, 서비스를 사용하면서 취하는 행동들입니다. 자잘한 모든 세부적 행동들을 적는 것이 아니라 단계별로 사용자가 어떤 행동을 취하는지에 집중해서 적으면 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;생각(Thoughts)&lt;/b&gt;: 행동을 하면서 사용자가 머릿속으로 떠오른 생각, 동기, 니즈같은 것들을 말합니다. 리서치 과정에서 사용자들이 했던 말들을 적어도 좋습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;감정(Feelings)&lt;/b&gt;: 감정은 하나의 곡선으로 보여지는데 만족스럽거나 즐거운 것처럼 긍정적인 감정이 들면 위로 올라가고 불만족스럽거나 실망스러운 부정적인 감정이 들면 아래로 내려갑니다. 선으로 표현하기 때문에 사용자가 어디 부분에서 만족스럽고 불만족스러웠는지를 한눈에 볼 수 있습니다.&lt;/li&gt;
&lt;/ul&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;문제점(Painpoints)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;행동, 생각, 감정들을 보았을때 시나리오(처한 상황)와 목표를 달성하는데 사용자가 불쾌하게 느낀 경험의 문제점들을 정리합니다.&amp;nbsp;&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;기회요소(Opportunities)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;사용자의 시나리오, 행동, 감정, 생각 등을 정리했으면 그 속에서 사용자가 좀 더 나은 경험을 누리기 위한 인사이트들을 뽑아내는 것을 말한다. 이 과정에서 디자이너의 통찰력과 다방면의 지식과 사고가 필요합니다.&amp;nbsp;&lt;/p&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;/li&gt;
&lt;li&gt;어떤 것이 가장 큰 기회요소일까?&lt;/li&gt;
&lt;li&gt;여정지도로 알게 된 것들로 무엇을 할 수 있을까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;WHY. 왜 사용자 여정지도가 필요한가요?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;01. &lt;b&gt;복잡한 사용자여정을 쉽게 이해&lt;/b&gt;할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;복잡한 프로세스를 시각적으로 표현함으로서, &lt;b&gt;정보의 명확성과 이해를 실천&lt;/b&gt;할 수 있습니다. 사용자 여정을 말로만 설명하려고 하는 것이 아니라, 이 지도를 통해 팀 전원이 사용자 체험 전체를 시각적으로 파악할 수 있습니다. 대부분의 고객 여정의 터치 포인트는 타임라인을 따라 매핑되어 프로세스의 각 단계에서 사용자의 요구를 시계열로 이해할 수 있습니다.&amp;nbsp;&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;b&gt;프로젝트와 관련된 모든 멤버가 고객 여정을 이해&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;02. &lt;b&gt;사용자 관점에서 계획을 설계&lt;/b&gt;할 수 있습니다.&amp;nbsp;&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;&amp;nbsp;&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;&amp;nbsp;&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;사용자 여정지도를 간단하게 보여주기 위해 템플릿을 만들어 보았습니다.&amp;nbsp; 템플릿에는 정답이 없습니다. 서비스 성격에 맞게 그리고 누가 보아도 이해가 쉽게 만들면 되고, 들어가는 구성요소도 서비스에 맞게 변경하셔서 만들면됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;두드레스 표지.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kCHtQ/btsC32NMSBp/p4hQXg0jnrTOwMGkbddNIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kCHtQ/btsC32NMSBp/p4hQXg0jnrTOwMGkbddNIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kCHtQ/btsC32NMSBp/p4hQXg0jnrTOwMGkbddNIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkCHtQ%2FbtsC32NMSBp%2Fp4hQXg0jnrTOwMGkbddNIk%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;1920&quot; height=&quot;1000&quot; data-filename=&quot;두드레스 표지.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>기획</category>
      <category>journey map</category>
      <category>사용자 여정지도</category>
      <category>여정지도</category>
      <author>나나나나나나나ㅏ나난ㄴ나ㅏ나나</author>
      <guid isPermaLink="true">https://become-a-developer.tistory.com/137</guid>
      <comments>https://become-a-developer.tistory.com/137#entry137comment</comments>
      <pubDate>Sat, 6 Jan 2024 18:21:42 +0900</pubDate>
    </item>
    <item>
      <title>[javascript] DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported. 해결하기</title>
      <link>https://become-a-developer.tistory.com/136</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;이미지 파일을 만든후 사용하려고 할때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;DOMException:&amp;nbsp;Failed&amp;nbsp;to&amp;nbsp;execute&amp;nbsp;'toDataURL'&amp;nbsp;on&amp;nbsp;'HTMLCanvasElement':&amp;nbsp;Tainted&amp;nbsp;canvases&amp;nbsp;may&amp;nbsp;not&amp;nbsp;be&amp;nbsp;exported.&lt;/i&gt;&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;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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 이미지를 가져올때 cross origin 문제로 발생한 오류이기때문에 이미지에 cross origin 을 &quot;Anonymous&quot; 로 지정해주면 된다&lt;/p&gt;
&lt;pre id=&quot;code_1694761117580&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const image = new Image();
image.src = image_src;
image.crossOrigin = 'Anonymous';
image.onload = () =&amp;gt; {
   consoel.log(image)
}&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;/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;✋ 잠깐 crossOrigin 추가 이후 갑자기 이미지 CORS 문제가 생긴다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://become-a-developer.tistory.com/128&quot;&gt;2022.11.23 - [WEB/JS] - 이미지 가져올때 CORS 오류가 발생한다....!!!!! [AWS S3+CDN]&lt;/a&gt;&lt;/p&gt;</description>
      <category>DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.</category>
      <category>image cors</category>
      <category>image crossorigin</category>
      <author>나나나나나나나ㅏ나난ㄴ나ㅏ나나</author>
      <guid isPermaLink="true">https://become-a-developer.tistory.com/136</guid>
      <comments>https://become-a-developer.tistory.com/136#entry136comment</comments>
      <pubDate>Fri, 15 Sep 2023 16:00:09 +0900</pubDate>
    </item>
    <item>
      <title>순수 css로 tooltip 만들기(with tailwind)</title>
      <link>https://become-a-developer.tistory.com/135</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;툴팁이란?&lt;/h4&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;p data-ke-size=&quot;size16&quot;&gt;생각보다 툴팁을 사용할 일이 많은데 따로 js 없이 순수하게 툴팁을 사용해보자&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;h4 data-ke-size=&quot;size20&quot;&gt;tailwind를 이용한 tooltip 예제&lt;/h4&gt;
&lt;pre id=&quot;code_1686218175535&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;relative group&quot;&amp;gt;
	&amp;lt;img :src=&quot;require('@/assets/icons/ico_report_problem.svg')&quot;
		class=&quot;w-[20px] h-[20px] cursor-pointer&quot;
		alt=&quot;problem&quot; /&amp;gt;
	&amp;lt;div class=&quot; group-hover:flex hidden hover:flex flex-col absolute top-[-150%] left-[50%] translate-x-[-50%] items-center justify-center text-center z-[20]&quot;&amp;gt;
		&amp;lt;div class=&quot;bg-[rgba(0,0,0,0.7)] text-white w-[140px] p-[8px] rounded-[6px] &quot;&amp;gt;Tooltip!&amp;lt;/div&amp;gt;
		&amp;lt;div class=&quot;h-0 w-0&quot; style=&quot;border-bottom: 5px solid transparent;border-top: 5px solid rgba(0,0,0,0.7);border-left: 5px solid transparent;border-right: 5px solid transparent;&quot; /&amp;gt;
	&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&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;size18&quot;&gt; tailwind에서 부모 hover시 자식 엘리먼트에게 무언가 옵션을 주고 싶다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 부모요소에 group 클래스를 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 자식 요소에 group-hover:flex 로 hover 관련 옵션을 추가&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;</description>
      <category>WEB/CSS</category>
      <category>CSS</category>
      <category>tailwind group</category>
      <category>tailwind parent hover</category>
      <category>taliwind</category>
      <category>tooltip</category>
      <category>tooltip css</category>
      <author>나나나나나나나ㅏ나난ㄴ나ㅏ나나</author>
      <guid isPermaLink="true">https://become-a-developer.tistory.com/135</guid>
      <comments>https://become-a-developer.tistory.com/135#entry135comment</comments>
      <pubDate>Thu, 8 Jun 2023 18:59:00 +0900</pubDate>
    </item>
    <item>
      <title>[javascript] 자바스크립트로 방금/몇초전/몇분전/몇시간전/몇달전/몇년전 출력하기</title>
      <link>https://become-a-developer.tistory.com/133</link>
      <description>&lt;pre id=&quot;code_1684298358740&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const today = new Date();
    const timeValue = new Date(date);


    const betweenTime = Math.floor((today.getTime() - timeValue.getTime()) / 1000 / 60);
    if (betweenTime &amp;lt; 1) return '방금전';
    if (betweenTime &amp;lt; 60) {
        return `${betweenTime}분전`;
    }

    const betweenTimeHour = Math.floor(betweenTime / 60);
    if (betweenTimeHour &amp;lt; 24) {
        return `${betweenTimeHour}시간전`;
    }

    const betweenTimeDay = Math.floor(betweenTime / 60 / 24);
    if (betweenTimeDay &amp;lt; 365) {
        return `${betweenTimeDay}일전`;
    }

    return `${Math.floor(betweenTimeDay / 365)}년전`;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>WEB/JS</category>
      <category>몇초전몇분전/몇시간전/몇달전/몇년전 출력하기</category>
      <author>나나나나나나나ㅏ나난ㄴ나ㅏ나나</author>
      <guid isPermaLink="true">https://become-a-developer.tistory.com/133</guid>
      <comments>https://become-a-developer.tistory.com/133#entry133comment</comments>
      <pubDate>Wed, 17 May 2023 13:39:31 +0900</pubDate>
    </item>
    <item>
      <title>ckeditor에서 youtube url 입력시 iframe으로 출력하기</title>
      <link>https://become-a-developer.tistory.com/132</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;ckeditor에서 유튜브를 embeded 했는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;embeded 태그로 입력되서 출력이 되지않는다ㅠㅠ&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;유튜브는 iframe으로 출력해야되서 그런데,&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;p data-ke-size=&quot;size16&quot;&gt;editor config에 넣어주면 된다!&lt;/p&gt;
&lt;pre id=&quot;code_1676955637338&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;editorConfig = {
    toolbar: [....],
    mediaEmbed: {
        previewsInData: true
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[vue 전체 코드]&lt;/p&gt;
&lt;pre id=&quot;code_1676955678694&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;ckeditor
            :editor=&quot;state.editor&quot; @ready=&quot;onReady&quot; @input=&quot;$emit('input', String(state.changeEditorData))&quot; v-model=&quot;state.changeEditorData&quot; :config=&quot;state.editorConfig&quot;/&amp;gt;
    &amp;lt;/div&amp;gt;

&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import UploadAdapter from '@/utils/CkEditorUploadAdapter';
import ClassicEditor from &quot;@ckeditor/ckeditor5-build-classic&quot;;
import CKEditor from '@ckeditor/ckeditor5-vue';
import {ref, watch} from &quot;vue&quot;;


export default {
    components: {
        ckeditor: CKEditor.component
    },
    name: &quot;CkEditor&quot;,
    props: ['editorData'],
    setup(props) {
        const state = ref({
            editor: ClassicEditor,
            changeEditorData: props.editorData,
            editorConfig: {
                // The configuration of the editor.
                mediaEmbed: {
                    previewsInData: true
                }
            }
        });

        const onReady = (editor) =&amp;gt; {
            editor.plugins.get('FileRepository').createUploadAdapter = (loader) =&amp;gt; {
                return new UploadAdapter(loader);
            }
        }
        watch(
            () =&amp;gt; props.editorData, (value) =&amp;gt; {
                state.value.changeEditorData = value;
            }
        )
        return {
            state,
            onReady,
        }
    },
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>WEB/JS</category>
      <category>ckeditor</category>
      <category>ckeditor youtube embeded</category>
      <category>youtube iframe</category>
      <author>나나나나나나나ㅏ나난ㄴ나ㅏ나나</author>
      <guid isPermaLink="true">https://become-a-developer.tistory.com/132</guid>
      <comments>https://become-a-developer.tistory.com/132#entry132comment</comments>
      <pubDate>Tue, 21 Feb 2023 14:02:02 +0900</pubDate>
    </item>
    <item>
      <title>누구나 할 수있는 자바스크립트에서 이미지 다운로드하기</title>
      <link>https://become-a-developer.tistory.com/131</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AWS S3에 올려둔 이미지를 다운로드해보자!&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;pre id=&quot;code_1672221060224&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let image = new Image();
image.onload = function() {
    const canvas = document.createElement(&quot;canvas&quot;);
    canvas.width = image.width;
    canvas.height = image.height;
    const ctx = canvas.getContext(&quot;2d&quot;);
    ctx.drawImage(image, 0, 0);

    let dataURL = canvas.toDataURL();
    
    const element = document.createElement('a');
    element.setAttribute('href', dataURL);
    element.setAttribute('download', '이미지 다운로드 완료');
    element.style.display = 'none';
    document.body.appendChild(element);
    element.click();
    document.body.removeChild(element);

}

image.crossOrigin = &quot;Anonymous&quot;;
image.width = 1500
image.height = 1500;
image.src = `${image_url}?not-from-cache-please`;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. AWS URL 로 이미지 파일 만들기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이미지 파일에서 BASE 64 추출하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. a 태그의 element 만들어서 다운로드 사용하기&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;474&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5MfhM/btrURVbH0T2/ySPIyqSMksOwd6LkvbIUc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5MfhM/btrURVbH0T2/ySPIyqSMksOwd6LkvbIUc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5MfhM/btrURVbH0T2/ySPIyqSMksOwd6LkvbIUc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5MfhM%2FbtrURVbH0T2%2FySPIyqSMksOwd6LkvbIUc0%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;474&quot; height=&quot;60&quot; data-origin-width=&quot;474&quot; data-origin-height=&quot;60&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 귀찮아서 그냥 해당 URL 로 이동하게 했는데, &lt;br /&gt;생각보다 간단하게 완료할 수 있었다&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;014&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/014.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/014.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 URL 이동말고 다운로드 폴더에 조심스레 넣어줘야겠다&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✋잠깐 혹시 AWS 파일 만들때 CORS 오류가 발생한다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://become-a-developer.tistory.com/128&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2022.11.23 - [WEB/JS] - 이미지 가져올때 CORS 오류가 발생한다....!!!!! [AWS S3+CDN]&lt;/a&gt;&lt;/p&gt;</description>
      <category>WEB/JS</category>
      <category>aws image download</category>
      <category>AWS 이미지 다운로드</category>
      <category>javascript image download</category>
      <category>js image download</category>
      <category>URL 이동말고 다운로드</category>
      <category>이미지 다운로드</category>
      <author>나나나나나나나ㅏ나난ㄴ나ㅏ나나</author>
      <guid isPermaLink="true">https://become-a-developer.tistory.com/131</guid>
      <comments>https://become-a-developer.tistory.com/131#entry131comment</comments>
      <pubDate>Wed, 28 Dec 2022 18:56:14 +0900</pubDate>
    </item>
  </channel>
</rss>