<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Eugenius1st.log</title>
        <link>https://velog.io/</link>
        <description>자신만의 속도로</description>
        <lastBuildDate>Mon, 15 Dec 2025 07:36:06 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Eugenius1st.log</title>
            <url>https://velog.velcdn.com/images/angel_eugnen/profile/b9287067-f0b6-4bab-a461-9a8a62e31518/KakaoTalk_20220408_210325466.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Eugenius1st.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/angel_eugnen" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Remix 걸음마]]></title>
            <link>https://velog.io/@angel_eugnen/Remix-%EA%B1%B8%EC%9D%8C%EB%A7%88</link>
            <guid>https://velog.io/@angel_eugnen/Remix-%EA%B1%B8%EC%9D%8C%EB%A7%88</guid>
            <pubDate>Mon, 15 Dec 2025 07:36:06 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/9b518cad-8ef6-44c7-bb36-48c66213aadb/image.png" alt=""></p>
<p>GPT생겨서 공부하기 참 쉬워졌쥬..</p>
<h2 id="1️⃣-공식문서-초기-세팅-이건-꼭-해라">1️⃣ 공식문서 초기 세팅 (이건 꼭 해라)</h2>
<p>✔️ 프로젝트 생성
<code>npx create-react-router@latest</code></p>
<p>Remix v2는 이제 React Router에 흡수됐어.
👉 Remix = React Router + 서버 기능 이라고 생각해.</p>
<h2 id="2️⃣-생성-후-구조부터-이해-중요-⭐⭐⭐">2️⃣ 생성 후 구조부터 이해 (중요 ⭐⭐⭐)</h2>
<p>처음 보면 이게 제일 멘붕이야.</p>
<pre><code>app/
 ├─ routes/
 │   ├─ _index.tsx
 │   ├─ login.tsx
 │   └─ posts.$id.tsx
 ├─ root.tsx
 └─ entry.server.tsx</code></pre><p>핵심만 딱 정리해줄게</p>
<ul>
<li>routes/ = 페이지</li>
<li>파일 이름 = URL</li>
</ul>
<p>이거 하나만 기억해도 반은 이해함</p>
<ul>
<li>routes/login.tsx   →  /login</li>
<li>routes/posts.$id.tsx → /posts/123</li>
</ul>
<p>👉 React Router의 <Route /> 안 써도 됨
👉 파일이 라우터다</p>
<h2 id="3️⃣-remix에서-제일-중요한-개념-top-4">3️⃣ Remix에서 제일 중요한 개념 TOP 4</h2>
<h3 id="🧠-①-loader-서버에서-데이터-가져오기">🧠 ① loader (서버에서 데이터 가져오기)</h3>
<pre><code class="language-js">export const loader = async () =&gt; {
  return { message: &#39;hello&#39; };
};</code></pre>
<p>✔️ 이 함수는 브라우저에서 실행 안 됨
✔️ 서버에서만 실행</p>
<pre><code class="language-js">const data = useLoaderData&lt;typeof loader&gt;();</code></pre>
<p>👉 API 호출 + 상태관리 + 로딩을 한 번에 해결</p>
<h3 id="🧠-②-action-폼-submit-처리">🧠 ② action (폼 submit 처리)</h3>
<pre><code class="language-js">export const action = async ({ request }) =&gt; {
  const formData = await request.formData();
  return null;
};</code></pre>
<p>✔️ POST / PUT / DELETE 전용
✔️ 서버에서 실행됨</p>
<p>👉 axios.post() + onSubmit 안 써도 됨</p>
<h3 id="🧠-③-form-remix-전용">🧠 ③ Form (Remix 전용)</h3>
<pre><code class="language-js">&lt;Form method=&quot;post&quot;&gt;
  &lt;input name=&quot;email&quot; /&gt;
  &lt;button type=&quot;submit&quot; /&gt;
&lt;/Form&gt;</code></pre>
<p>👉 submit 하면 자동으로 action() 실행
👉 새로고침 ❌
👉 상태관리 ❌</p>
<h3 id="🧠-④-usenavigation-로딩-상태">🧠 ④ useNavigation (로딩 상태)</h3>
<pre><code class="language-js">const navigation = useNavigation();

navigation.state === &#39;loading&#39;
navigation.state === &#39;submitting&#39;</code></pre>
<p>👉 버튼 로딩 처리, 스피너 여기서 함</p>
<h3 id="4️⃣-아-그래서-뭐가-좋은데-리액트랑-비교">4️⃣ “아 그래서 뭐가 좋은데?” (리액트랑 비교)</h3>
<table>
<thead>
<tr>
<th>리액트</th>
<th>Remix</th>
</tr>
</thead>
<tbody><tr>
<td>useEffect로 API 호출</td>
<td>loader에서 서버 호출</td>
</tr>
<tr>
<td>axios + 상태관리</td>
<td>기본 제공</td>
</tr>
<tr>
<td>폼 submit 직접 처리</td>
<td>action 자동</td>
</tr>
<tr>
<td>로딩 상태 직접 관리</td>
<td>navigation</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS]]></title>
            <link>https://velog.io/@angel_eugnen/NestJS</link>
            <guid>https://velog.io/@angel_eugnen/NestJS</guid>
            <pubDate>Fri, 12 Dec 2025 15:54:15 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/916c1116-6fe6-44d2-89f3-f2083d53ad91/image.png" alt=""></p>
<h2 id="시작-전-환경-세팅">시작 전 환경 세팅</h2>
<h3 id="1️⃣-필수-도구">1️⃣ 필수 도구</h3>
<p>Insomnia
→ API 테스트용 (Postman 대체)</p>
<h3 id="2️⃣-nestjs-cli-설치-및-프로젝트-생성">2️⃣ NestJS CLI 설치 및 프로젝트 생성</h3>
<p><code>npm i -g @nestjs/cli
nest new project-name</code></p>
<p>nest new 명령어를 사용하면
기본적인 폴더 구조와 설정이 자동으로 구성된다.</p>
<hr>
<h2 id="single-responsibility-principle-srp">Single Responsibility Principle (SRP)</h2>
<p>NestJS는 단일 책임 원칙(SRP) 을 굉장히 강하게 따른다.</p>
<p>하나의 module / class / function 은 하나의 책임만 가져야 한다</p>
<h3 id="기본-구조-예시">기본 구조 예시</h3>
<blockquote>
</blockquote>
<p>AppModule
 ├── AppController
 └── AppService</p>
<h3 id="역할-분리">역할 분리</h3>
<ol>
<li>Controller</li>
</ol>
<ul>
<li>HTTP 요청/응답 처리</li>
<li>URL, Method 정의</li>
</ul>
<ol start="2">
<li>Service</li>
</ol>
<ul>
<li>실제 비즈니스 로직 담당</li>
</ul>
<ol start="3">
<li>Module</li>
</ol>
<ul>
<li>관련 Controller와 Provider를 묶는 단위</li>
</ul>
<p>👉
Controller에서 로직을 처리하지 않고
Service로 위임하는 구조가 기본이다.</p>
<hr>
<h2 id="validationpipe-요청-데이터-검증">ValidationPipe (요청 데이터 검증)</h2>
<p>NestJS는 class 기반 유효성 검사를 공식적으로 지원한다.</p>
<p>1️⃣ 패키지 설치
<code>npm i class-validator class-transformer</code></p>
<p><code>class-validator</code> : 유효성 검사</p>
<p><code>class-transformer</code> : plain object → class 변환</p>
<p>2️⃣ DTO를 위한 패키지
<code>npm i @nestjs/mapped-types</code></p>
<p>DTO 상속 및 변환을 도와주는 유틸 패키지</p>
<p>3️⃣ DTO 예시</p>
<pre><code class="language-js">export class CreateMovieDto {
  @IsString()
  title: string;

  @IsNumber()
  year: number;
}</code></pre>
<p>👉
ValidationPipe를 사용하면</p>
<p>컨트롤러 진입 전에 자동으로 유효성 검증이 이루어진다.</p>
<hr>
<h2 id="dependency-injection-di">Dependency Injection (DI)</h2>
<p>NestJS의 가장 핵심 개념 중 하나.</p>
<h3 id="nestjs의-di-기본-구조--핵심-3요소">NestJS의 DI 기본 구조 – 핵심 3요소</h3>
<p>역할 - 의미
Provider    - 주입 가능한 클래스
Consumer    - 주입받는 쪽
Module    - Provider를 등록하는 장소</p>
<p>예시</p>
<pre><code class="language-js">@Injectable()
export class MoviesService {}

@Module({
  providers: [MoviesService],
})
export class MoviesModule {}

@Controller(&#39;movies&#39;)
export class MoviesController {
  constructor(private readonly moviesService: MoviesService) {}
}</code></pre>
<p>👉
객체를 직접 생성하지 않고
NestJS가 DI 컨테이너를 통해 자동으로 주입해준다.</p>
<hr>
<h2 id="fastify-vs-express">Fastify vs Express</h2>
<p>NestJS는 기본적으로 Express 위에서 동작한다</p>
<p>하지만 Fastify를 사용하면
Express 대비 약 2배 빠른 성능을 낼 수 있다.</p>
<p>Req / Res 직접 접근
getAll(@Req() req, @Res() res): GetMovieDto[] {
  return this.movies;
}</p>
<p>NestJS에서는 @Req(), @Res() 데코레이터를 통해
플랫폼(Express / Fastify)의 객체에 직접 접근할 수 있다.</p>
<h3 id="❗-하지만-권장되지-않는-이유">❗ 하지만 권장되지 않는 이유</h3>
<p>NestJS의 추상화 레이어를 깨게 됨</p>
<p>플랫폼 종속 코드가 됨</p>
<p>테스트 및 유지보수 어려움 증가</p>
<p>👉
가능하면 NestJS 방식(@Body, @Param, @Query 등)을 사용하는 것이 좋다.</p>
<hr>
<h3 id="정리하며-느낀-점">정리하며 느낀 점</h3>
<p>NestJS는 구조와 규칙을 강하게 강제하는 프레임워크</p>
<p>처음엔 번거롭지만 규모가 커질수록 장점이 커진다</p>
<p>특히 DI + Module 구조는 테스트, 유지보수, 협업에 굉장히 유리하다</p>
<p>React 개발자 입장에서는
“왜 이렇게 나눠?” 싶지만
커질수록 이 구조가 왜 필요한지 체감하게 되겠지</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TinyMCE webpack 최적화 구현 계획 (하이브리드 방식)]]></title>
            <link>https://velog.io/@angel_eugnen/TinyMCE-webpack-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B5%AC%ED%98%84-%EA%B3%84%ED%9A%8D-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@angel_eugnen/TinyMCE-webpack-%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B5%AC%ED%98%84-%EA%B3%84%ED%9A%8D-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Tue, 09 Dec 2025 02:29:10 GMT</pubDate>
            <description><![CDATA[<p>기존 코드에서 <code>tinymce/tinymce-react</code> 를 <code>CDN</code>방식으로 이용하고 있었다.</p>
<p>이유는, </p>
<blockquote>
<ul>
<li>프로젝트에 파일을 포함하지 않아도 됨</li>
</ul>
</blockquote>
<ul>
<li>캐싱 활용 가능 (다른 사이트에서도 같은 CDN 사용 시)</li>
<li>초기 설정이 간단
... 가장 큰 이유는 회사에서 빨리 HTML &lt;-&gt; 에디터 읽기쓰기 호환 기능을 가진 에디터를 도입해달라고 했기 때문.</li>
</ul>
<p>하지만 React에서 CDN으로 불러오면 아래와 같은 단점이 있어서 npm방식으로 바꾸려 한다.</p>
<blockquote>
<ul>
<li>CDN 서버 장애 시 사용 불가</li>
</ul>
</blockquote>
<ul>
<li>버전 관리가 명확하지 않음 (URL에 버전이 있어도 관리가 어려움)</li>
<li>보안 이슈 가능성 (외부 스크립트 실행)</li>
<li>번들러 최적화 불가</li>
</ul>
<p>아지만 이렇게 하다보니
예전에 써서 문제가 없던 Quill에디터는 
모노리식 번들(단일번들) 이라 css만 import하면 동작하고, 모든것을 webpack이 처리해서 문제가 없었다고 한다. 
TinyMCE 라는 라이브러리는 모듈러 아키텍처라 필요한 리소스를 런타임에 로드하기 때문에 React 에서 사용시 문제가 생겼다. 이 라이브러리의 유연성과 확장성 때문에 리소스 경로설정 이라는 것이 필요했다.</p>
<p>추가로 번들 크기가 증가할것이다 라는 문제점이 또 보였고..</p>
<p>결국 CRACO/webpack 설정을 하기로 했다.</p>
<hr>
<h1 id="tinymce-webpack-최적화-구현-계획-하이브리드-방식-개요">TinyMCE webpack 최적화 구현 계획 (하이브리드 방식) 개요</h1>
<h2 id="목표">목표</h2>
<ul>
<li>TinyMCE를 npm 패키지로 사용 (CDN 제거)</li>
<li>공통 webpack 설정 + 앱별 오버라이드 구조</li>
<li>webpack으로 리소스 최적화 및 번들 크기 감소</li>
<li>seller-admin에 먼저 적용 (테스트 후 system-admin 확장)
구현 단계</li>
</ul>
<h3 id="1-craco-설치-및-공통-설정">1. CRACO 설치 및 공통 설정</h3>
<ul>
<li>apps/seller-admin/package.json에 @craco/craco 의존성 추가</li>
<li>루트에 공통 설정 생성: craco.base.js</li>
<li>TinyMCE 리소스 복사 설정 (CopyWebpackPlugin)</li>
<li>공통 번들 최적화 설정 (압축, 트리쉐이킹, 코드 스플리팅)</li>
<li>apps/seller-admin/craco.config.js 생성</li>
<li>공통 설정 import 및 seller-admin 특화 오버라이드 (필요시)</li>
<li>apps/seller-admin/package.json의 scripts를 react-scripts → craco로 변경<h3 id="2-nodejs-버전-고정">2. Node.js 버전 고정</h3>
</li>
<li>루트에 .nvmrc 파일 생성 (Node.js 24.6.0)</li>
<li>Jenkins 배포 시 버전 일관성 보장<h3 id="3-tinymce-컴포넌트-수정">3. TinyMCE 컴포넌트 수정</h3>
</li>
<li>packages/ui-components/src/organisms/TinyMCEEditor.tsx 수정</li>
<li>base_url 설정 제거 (webpack이 처리)</li>
<li>tinymceScriptSrc prop 제거 (npm 패키지 사용)</li>
<li>필요한 플러그인만 import하여 트리쉐이킹 최적화<h3 id="4-빌드-스크립트-검증">4. 빌드 스크립트 검증</h3>
</li>
<li>pnpm install 실행</li>
<li>pnpm --filter seller-admin start로 개발 서버 테스트</li>
<li>pnpm --filter seller-admin build로 프로덕션 빌드 테스트</li>
</ul>
<h2 id="주요-파일-변경">주요 파일 변경</h2>
<h3 id="1-appsseller-adminpackagejson">1. apps/seller-admin/package.json</h3>
<ul>
<li>@craco/craco 의존성 추가</li>
<li>scripts 수정: react-scripts → craco<h3 id="2-cracobasejs-신규-루트">2. craco.base.js (신규, 루트)</h3>
</li>
<li>공통 webpack 설정</li>
<li>TinyMCE 리소스 복사 (CopyWebpackPlugin)</li>
<li>공통 번들 최적화 설정<h3 id="3-appsseller-admincracoconfigjs-신규">3. apps/seller-admin/craco.config.js (신규)</h3>
</li>
<li>공통 설정 import</li>
<li>seller-admin 특화 설정 오버라이드 (필요시)<h3 id="4-nvmrc-신규">4. .nvmrc (신규)</h3>
</li>
<li>Node.js 24.6.0 고정<h3 id="5-packagesui-componentssrcorganismstinymceeditortsx">5. packages/ui-components/src/organisms/TinyMCEEditor.tsx</h3>
</li>
<li>base_url 제거</li>
<li>tinymceScriptSrc 제거</li>
</ul>
<h2 id="아키텍처">아키텍처</h2>
<pre><code>루트/
├── craco.base.js          # 공통 webpack 설정
├── .nvmrc                  # Node.js 버전 고정
└── apps/
    └── seller-admin/
        └── craco.config.js # 공통 설정 import + 오버라이드</code></pre><h2 id="예상-효과">예상 효과</h2>
<ul>
<li>번들 크기: 약 20-30% 감소 (트리쉐이킹 및 최적화)</li>
<li>로딩 속도: 개선 (로컬 파일, 번들 최적화)</li>
<li>안정성: 향상 (외부 CDN 의존성 제거)</li>
<li>유지보수성: 향상 (공통 설정 중앙 관리)<h2 id="주의사항">주의사항</h2>
Jenkins 배포 시 Node.js 24.6.0 이상 필요
빌드 메모리 사용량 증가 가능 (webpack 최적화)
seller-admin 테스트 완료 후 system-admin에도 동일 적용</li>
</ul>
<hr>
<h2 id="코드">코드</h2>
<h3 id="1-appssystem-adminpackagejson-수정">1. apps/system-admin/package.json 수정</h3>
<p>script 에서
&#39;start&#39;:&#39;react-script start&#39; 와 같은 것들을 아래처럼 바꾸고</p>
<ul>
<li>&quot;prestart&quot;: &quot;node ../../scripts/copy-tinymce.js seller-admin&quot;,</li>
<li>&quot;start&quot;: &quot;craco start&quot;,</li>
<li>&quot;build&quot;: &quot;craco build&quot;,</li>
<li>&quot;test&quot;: &quot;craco test&quot;,</li>
</ul>
<p>devDependencies에서 아래와 같은 것들을 설치</p>
<ul>
<li>&quot;@craco/craco&quot;: &quot;^7.1.0&quot;,</li>
<li>&quot;copy-webpack-plugin&quot;: &quot;^12.0.2&quot;,</li>
</ul>
<h3 id="2-appssystem-admincracoconfigjs-생성">2. apps/system-admin/craco.config.js 생성</h3>
<pre><code class="language-js">const baseConfig = require(&#39;../../craco.base.js&#39;);

module.exports = {
  ...baseConfig,
  // system-admin 특화 설정이 필요한 경우 여기에 추가
  // 예: 특정 플러그인, 로더 설정 등
};
</code></pre>
<h3 id="3-루트-스크립트에-copy-tinymcejs-생성">3. 루트 스크립트에 copy-tinymce.js 생성</h3>
<pre><code class="language-js">const fs = require(&#39;fs&#39;);
const path = require(&#39;path&#39;);

// 루트 디렉토리 찾기 (craco.base.js와 동일한 기준)
const rootDir = path.resolve(__dirname, &#39;../../..&#39;);

// 여러 가능한 경로 시도
const possibleSources = [
  // 1. 루트의 node_modules (craco.base.js와 동일한 방식)
  path.resolve(rootDir, &#39;node_modules/tinymce&#39;),
  // 2. pnpm의 .pnpm 디렉토리에서 찾기
  (() =&gt; {
    try {
      const pnpmStore = path.resolve(rootDir, &#39;node_modules/.pnpm&#39;);
      if (fs.existsSync(pnpmStore)) {
        // tinymce@로 시작하는 디렉토리 찾기
        const entries = fs.readdirSync(pnpmStore);
        for (const entry of entries) {
          if (entry.startsWith(&#39;tinymce@&#39;)) {
            const entryPath = path.join(pnpmStore, entry);
            if (fs.statSync(entryPath).isDirectory()) {
              const tinymcePath = path.join(entryPath, &#39;node_modules/tinymce&#39;);
              if (fs.existsSync(tinymcePath)) {
                return tinymcePath;
              }
            }
          }
        }
      }
      return null;
    } catch (e) {
      return null;
    }
  })(),
  // 3. ui-components의 node_modules
  path.resolve(rootDir, &#39;packages/ui-components/node_modules/tinymce&#39;),
  // 4. system-admin의 node_modules
  path.resolve(__dirname, &#39;../node_modules/tinymce&#39;),
];

// 존재하는 소스 경로 찾기
let source = null;
for (const possibleSource of possibleSources) {
  if (possibleSource &amp;&amp; fs.existsSync(possibleSource)) {
    source = possibleSource;
    break;
  }
}

if (!source) {
  console.error(&#39;❌ TinyMCE not found in node_modules&#39;);
  console.error(&#39;   Tried paths:&#39;);
  possibleSources.forEach((p, i) =&gt; {
    if (p) {
      const exists = fs.existsSync(p) ? &#39;✓&#39; : &#39;✗&#39;;
      console.error(`   ${i + 1}. ${exists} ${p}`);
    } else {
      console.error(`   ${i + 1}. (skipped)`);
    }
  });
  console.error(&#39;\n   Please ensure TinyMCE is installed:&#39;);
  console.error(&#39;   pnpm install&#39;);
  console.error(&#39;\n   Or check if TinyMCE is in packages/ui-components:&#39;);
  console.error(&#39;   ls packages/ui-components/node_modules/tinymce&#39;);
  process.exit(1);
}

const dest = path.resolve(__dirname, &#39;../public/tinymce&#39;);
const checkFile = path.resolve(dest, &#39;plugins/help/js/i18n/keynav/en.js&#39;);

// 파일이 없을 때만 복사 (빠름)
if (!fs.existsSync(checkFile)) {
  console.log(&#39;📦 Copying TinyMCE files (first time only)...&#39;);
  console.log(`   From: ${source}`);
  console.log(`   To: ${dest}`);

  const { execSync } = require(&#39;child_process&#39;);

  if (fs.existsSync(dest)) {
    fs.rmSync(dest, { recursive: true, force: true });
  }

  const isWindows = process.platform === &#39;win32&#39;;
  if (isWindows) {
    execSync(`xcopy /E /I /Y &quot;${source}&quot; &quot;${dest}&quot;`, { stdio: &#39;inherit&#39; });
  } else {
    execSync(`cp -R &quot;${source}&quot; &quot;${dest}&quot;`, { stdio: &#39;inherit&#39; });
  }
  console.log(&#39;✅ Done&#39;);
} else {
  console.log(&#39;⚡ TinyMCE files already exist&#39;);
}
</code></pre>
<h2 id="파일별-용도-설명">파일별 용도 설명</h2>
<h3 id="1-cracobasejs---webpack-설정-파일">1. craco.base.js - Webpack 설정 파일</h3>
<ul>
<li>용도: CRACO를 통한 webpack 설정</li>
<li>역할:
개발/프로덕션 모드에서 webpack이 실행될 때 동작
CopyWebpackPlugin으로 TinyMCE 리소스를 빌드 출력에 복사
프로덕션 빌드 최적화 (코드 스플리팅, 트리쉐이킹, 압축)</li>
<li>작동 시점:</li>
<li>craco start 실행 시 (개발 모드)</li>
<li>craco build 실행 시 (프로덕션 빌드)</li>
<li>🔴 문제점:
개발 모드에서 CopyWebpackPlugin이 초기 복사를 보장하지 않을 수 있음
개발 서버 시작 시 파일이 없으면 에러 발생 가능!!!<h3 id="2-scriptscopy-tinymcejs---파일-복사-스크립트">2. scripts/copy-tinymce.js - 파일 복사 스크립트</h3>
</li>
<li>용도: 개발 서버/빌드 시작 전에 TinyMCE 파일을 수동 복사</li>
<li>역할:
prestart, prebuild 훅에서 실행
TinyMCE 파일이 없을 때만 복사 (조건부)
개발 모드에서 리소스 누락 방지</li>
<li>작동 시점:
pnpm start 실행 전 (prestart 훅)
pnpm build 실행 전 (prebuild 훅)</li>
<li>장점:
개발 서버 시작 전에 파일 존재 보장
파일이 있으면 스킵하여 빠른 시작
에러 메시지로 문제 진단 용이</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Deployment-Vercel]]></title>
            <link>https://velog.io/@angel_eugnen/Deployment-Vercel</link>
            <guid>https://velog.io/@angel_eugnen/Deployment-Vercel</guid>
            <pubDate>Sun, 13 Jul 2025 08:42:37 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/e1c0ff5f-ccf1-43ea-8656-87013a00da10/image.png" alt=""></p>
<h2 id="deployment">Deployment</h2>
<p>git에 push 를 마쳤다면,
Vercel이 호출할 수 있도록 package.json 에 명령어를 수정한다</p>
<pre><code class="language-bash">  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;next dev&quot;,
    &quot;build&quot;: &quot;next build&quot;,
    &quot;start&quot;:&quot;next start&quot;
  },</code></pre>
<p>vercel 은 그냥 deploy 버튼만 누르고 기다리면 된다.
그리고 나타나는 오류들을 해결하면 dashboard에 보인다.</p>
<hr>
<p>추가)
<code>&lt;Link prefetch href={</code>/movies/${id}<code>}&gt;{title}&lt;/Link&gt;</code></p>
<p>배포 시에 prefetch 라는 props 통해  통해 사용자가 클릭하기 전부터 fetch를 진행한다.
스크롤을 내리고 아래쪽에 있는 링크가 보여지면, NextJS는 자동적으로 요청을 보내게 된다.
movie페이지를 자동으로 fetch 하고 있는 것이다. 클릭하지 않았지만 마치 페이지에 들어간 것처럼 요청하는 것이다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CSS Modules]]></title>
            <link>https://velog.io/@angel_eugnen/CSS-Modules</link>
            <guid>https://velog.io/@angel_eugnen/CSS-Modules</guid>
            <pubDate>Sun, 13 Jul 2025 08:06:41 GMT</pubDate>
            <description><![CDATA[<h2 id="css-module">Css Module</h2>
<ol>
<li><p>application 전체에 적용할 global style 을 만든다.
전체 웹사디트의 bg color 나 폰트를 적용하도록 한다. </p>
</li>
<li><p>app/layout 에서 import global css를 한다.</p>
</li>
<li><p>공통 디자인을 적용해준다.</p>
<pre><code class="language-css">html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: &quot;&quot;;
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
</code></pre>
</li>
</ol>
<p>body {
  font-family: Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;
  background-color: black;
  color: white;
  font-size: 18px;
}</p>
<p>a {
  color: inhefit;</p>
<p>  text-decoration: none;
}</p>
<p>a:hover {
  text-decoration: underline;
}</p>
<pre><code>
4. 특정 디자인에 적용하고 싶다면 이름에 `.module.css`를 넣도록 한다.
![](https://velog.velcdn.com/images/angel_eugnen/post/0bbb9e61-c53d-4b38-860e-eb0e403d3359/image.png)

폴더 위치는 /styles 폴더에 있어도 무관하다.

그리고 오로지 className만 작성할 것이다.

5. 그리고 사용하고 싶은 tsx 파일에서 css 파일을 마치 자바스크립트 처럼 import 해온다.
`import styles from &#39;../style/navigation.module.css&#39;`
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Error Handling]]></title>
            <link>https://velog.io/@angel_eugnen/Error-Handling</link>
            <guid>https://velog.io/@angel_eugnen/Error-Handling</guid>
            <pubDate>Sun, 13 Jul 2025 06:52:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/e66901f8-92eb-4358-aa21-0e93848deae1/image.png" alt=""></p>
<h2 id="error-handling">Error Handling</h2>
<ol>
<li>API 요청이 안되거나</li>
<li>네트워크가 끊기는 등
에러가 생긴 경우의 핸들링은
error.tsx 파일을 만들면 된다.</li>
</ol>
<p>프레임워크 특성상 파일 위치는 페이지 대상이 되는 폴더 안에 넣어야 한다.</p>
<pre><code class="language-js">&quot;use client&quot;;
export default function ErrorOMG() {
  return &lt;h1&gt;Error OMG&lt;/h1&gt;;
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Parallel Request]]></title>
            <link>https://velog.io/@angel_eugnen/Parallel-Request</link>
            <guid>https://velog.io/@angel_eugnen/Parallel-Request</guid>
            <pubDate>Sun, 13 Jul 2025 06:37:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/c6c583dd-0fe0-4df7-ba78-c49f6c0169a3/image.png" alt=""></p>
<h2 id="parallel-request">Parallel Request</h2>
<pre><code class="language-js">import { API_URL } from &quot;../../../(home)/page&quot;;
async function getMovie(id: string) {
  console.log(`getMovie: ${Date.now()}`);
  await new Promise((resolve) =&gt; setTimeout(resolve, 5000));
  const response = await fetch(`${API_URL}/${id}`);
  const json = await response.json();
  return json;
}
async function getVideo(id: string) {
  console.log(`getVideo: ${Date.now()}`);
  await new Promise((resolve) =&gt; setTimeout(resolve, 5000));
  const response = await fetch(`${API_URL}/${id}/videos`);
  const json = await response.json();
  return json;
}
export default async function Text({ params }) {
  const { id } = await params;
  console.log(&quot;startfetching&quot;);
  const movie = await getMovie(id); // 이 함수 응답이 오래걸리면
  const video = await getVideo(id); // 첫줄 함수를 기다리다가 응답이 늦는다. 병렬로 실행해야 한다.
  console.log(&quot;endfetching&quot;);

  return (
    &lt;div&gt;
      testestest
      &lt;h1&gt;{movie.title}&lt;/h1&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>이렇게 하면 
start fetching 이 출력되고 end fetching이 순차적으로 출력되며
총 10초 넘게 걸리게 된다. </p>
<pre><code class="language-bash">startfetching
getMovie: 1752386606417
getVideo: 1752386607056
endfetching</code></pre>
<p>병렬 요청으로 바꿔줘야 한다.</p>
<blockquote>
<p><code>promise.all</code>을 사용하면 결과값이 동시에 나올 것이다.</p>
</blockquote>
<pre><code class="language-js">const [movie, videos]=  await Promise.all([getMovie(id), getVideos(id)])</code></pre>
<p>이렇게 요청을 보내면</p>
<pre><code class="language-bash">startfetching
getMovie: 1752386815316
getVideo: 1752386815316 
endfetching</code></pre>
<p>동시에 시작하게 된다.
순차적인 작업이 아니기 때문이다. 이것이 병렬 fetch 이다.</p>
<p>하지만 지금은 둘이 끝나야 보이므로, 둘을 분리하는 것이 좋다.
둘중 어느 한쪽도 기다릴 필요가 없게! 그것이 suspense이다.</p>
<h2 id="suspense">Suspense</h2>
<p>getMovie와 getVideos 만 렌더링 하는 컴포넌트를 만든다.
/components/movie-video.tsx 
/components/movie-info.tsx </p>
<pre><code class="language-js">// 1. components.movie-video.tsx
import { API_URL } from &quot;../app/(home)/page&quot;;
async function getVideos(id: string) {
  console.log(`getVideo: ${Date.now()}`);
  await new Promise((resolve) =&gt; setTimeout(resolve, 5000));
  const response = await fetch(`${API_URL}/${id}/videos`);
  const json = await response.json();
  return json;
}

export default async function MovieVideos({ id }: { id: string }) {
  const videos = await getVideos(id);
  return &lt;h6&gt;{JSON.stringify(videos)}&lt;/h6&gt;;
}

//2. components/movie-video.tsx
import { API_URL } from &quot;../app/(home)/page&quot;;
async function getMovie(id: string) {
  console.log(`getMovie: ${Date.now()}`);
  await new Promise((resolve) =&gt; setTimeout(resolve, 3000));
  const response = await fetch(`${API_URL}/${id}`);
  const json = await response.json();
  return json;
}

export default async function MovieInfo({ id }: { id: string }) {
  const movie = await getMovie(id);
  return &lt;h6&gt;{JSON.stringify(movie)}&lt;/h6&gt;;
}
</code></pre>
<p>를 만들고 개별적으로 기다리게 한다.
그리고 page.tsx에서 MovieInfo 와 MovieVideos를 렌더링한다.</p>
<pre><code class="language-js">import { Suspense } from &quot;react&quot;;
import MovieDetail from &quot;../../../../components/movie-info&quot;;
import MovieVideos from &quot;../../../../components/movie-video&quot;;
export default async function Text({ params }) {
  const { id } = await params;



  return (
    &lt;div&gt;
      &lt;Suspense fallback={&lt;h1&gt;Loading Movie Info&lt;/h1&gt;}&gt;
        &lt;MovieDetail id={id} /&gt;
      &lt;/Suspense&gt;
      &lt;Suspense fallback={&lt;h1&gt;Loading Movie Video&lt;/h1&gt;}&gt;
        &lt;MovieVideos id={id} /&gt;
      &lt;/Suspense&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>그럼 순차적으로 데이터가 준비되는 순간 사용자는 렌더링된 데이터를 볼 수 있다.
그리고 page.tsx 와 같은 위치에 있는 loading.tsx 는 최적화 한 덕분에 await 하는게 없으므로 전체 로딩하는 일이 없게 된다.</p>
<blockquote>
<p>page 단위 로딩: <code>loading.tsx</code>
서버컴포넌트 단위 로딩: <code>Suspense</code></p>
</blockquote>
<hr>
<p>Next15) 15버전 부터는 기본 캐싱이 안되기 때문에 캐싱을 확인하려면 fetch의 두번째 인자 cache옵션을 <code>force-cache</code> 로 바꿔줘야 한다고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Data Fetching]]></title>
            <link>https://velog.io/@angel_eugnen/Data-Fetching</link>
            <guid>https://velog.io/@angel_eugnen/Data-Fetching</guid>
            <pubDate>Sun, 13 Jul 2025 05:49:18 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/eb7cdb7a-b6bb-43c1-902c-9479c0262562/image.png" alt=""></p>
<h1 id="data-fetching">Data Fetching</h1>
<p>fetch, streaming, suspense, boundary 등을 배울것이다.
쉽고 멋지다고 한다.</p>
<p>NextJS 의 fetching을 위해 영화 상세보기 API를 이용하자.</p>
<blockquote>
<p>[출처-노마드코더] :  <a href="https://nomad-movies.nomadcoders.workers.dev/">https://nomad-movies.nomadcoders.workers.dev/</a></p>
</blockquote>
<p>/: This page
/movies: List popular movies
/movies/:id: Get movie by :id
/movies/:id/credits: Get credits for a movie by :id
/movies/:id/videos: Get videos for a movie by :id
/movies/:id/providers: Get providers for a movie by :id
/movies/:id/similar: Get similar movies for a movie by :id</p>
<p>url 종류는 위와 같다.</p>
<hr>
<h2 id="react-client-fetching">React Client Fetching</h2>
<pre><code class="language-js">&quot;use client&quot;;
// 과거에 리액트에서 하던 방식의 클라이언트 fetch는 아래와 같다.
import { useEffect, useState } from &quot;react&quot;;

export default function HOME() {
  const [isLoading, setIsLoading] = useState(true);
  const [movies, setMovies] = useState([]);
  const getMovies = async () =&gt; {
    const response = await fetch(
      &quot;https://nomad-movies.nomadcoders.workers.dev/movies&quot;
    );
    const json = await response.json();
    setMovies(json);
    setIsLoading(false);
  };
  useEffect(() =&gt; {
    getMovies();
  }, []);
  return (
    &lt;div&gt;
      &lt;h1&gt;HOME&lt;/h1&gt;
      &lt;p&gt;{isLoading ? &quot;...isLoading&quot; : JSON.stringify(movies)}&lt;/p&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p>기존방식: React API &lt;===&gt; API &lt;===&gt; DB
이렇게 하면 네트워크 탭을 열면 누구나 api를 보고 쿼리를 통해 정보를 찾을 수 있어서 보안적으로 안전하지 않은 경우가 있다.</p>
<p>모든것을 client 에서 fetching을 하면 API에서 보안을 위해 DB요청하는 중간의 API이 필요했다. 하지만 NextJS 는 중간의 백엔드 API 없이 DB에서 바로 데이터를 가져올 수 있다.</p>
<p>또한 React앱에서는 로딩 상태를 항상 신경써서 직접 만들어야 하고, useState를 사용해서 데이터를 관리해야 한다. </p>
<p>하지만 server component 에서 fetching을 하면, useEffect와 useState, Loding을 사용하지 않아도 된다.</p>
<p>그리고 프론트엔드 개발자는 백엔드가 만든 API가 필요하지 않게 된다.</p>
<pre><code class="language-js">//next.js
// 브라우저에서는 아무것도 fetch하지 않는다.
export const metadata = {
  title: &quot;Home&quot;,
};
const MoviesURL = &quot;https://nomad-movies.nomadcoders.workers.dev/movies&quot;;

async function getMovies() {
  const response = await fetch(MoviesURL);
  const json = await response.json();
  return json;
}
// 한번만 서버에서 fetch 하면 캐시된다. server component 에서 Next JS 가 fetch한 것을 기억하고 있기 때문이다.
// 새로고침해도 이미 응답이 캐시되었기 때문에 로딩이 필요 없다
// 하지만 만약 서버 끄고 재 실행된다면 로딩이 필요하다. 즉 첫번째 fetch한 데이터가 존재하면 API에 요청하지 않고 캐싱된 데이터를 보여준다.
export default async function HomePage() {
  const movies = await getMovies();
  return &lt;div&gt;{JSON.stringify(movies)}&lt;/div&gt;;
}
</code></pre>
<blockquote>
<p>최신 데이터가 필요할때는 캐싱이나 revalidation을 해야한다.</p>
</blockquote>
<hr>
<h2 id="loding">Loding</h2>
<p>백엔드의 응답까지 첫 초기 렌더링이 UI 가 표기되지 않는 멈춤상태로 있는 설정이 싫고 최소한의 정적 UI를(네비게이션) 보고있게 하고 싶다면 loading파일을 하나 만들어서 해결할 수 있다. loading.tsx 파일을 만들어주자</p>
<pre><code class="language-js">export default function Loading() {
  return &lt;h2&gt;Loading...&lt;/h2&gt;;
}
</code></pre>
<p>loading 파일만 제공 해줘도 된다. 
이것은 백엔드가 페이지를 streaming 하기 때문에 가능하다.
백엔드에서 fetch함수가 완료되면 결과값을 브라우저에 보낸다. 그래서 layout과 navigation을 먼저 보내고 loading이 끝난 결과값을 보여주면 된다.</p>
<p>네트워크 탭을 열면 로딩중이라면 localhost가 로딩중임을 확인 할 수 있다. 이때 NextJS는 브라우저의 일부를 보여주며 기다려 달라고 하고, loading컴포넌트가 교체되는 것이다. 그리고 HomePage가 async인 이유는 준비된 Html 부분을 브라우저에 전달하기 위함이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dynamic Routes]]></title>
            <link>https://velog.io/@angel_eugnen/Dynamic-Routes</link>
            <guid>https://velog.io/@angel_eugnen/Dynamic-Routes</guid>
            <pubDate>Thu, 10 Jul 2025 02:48:11 GMT</pubDate>
            <description><![CDATA[<h2 id="dynamic-routes">Dynamic Routes</h2>
<p>변수가 라우터에 들어갈 때 사용한다.
파일시스템으로 동작한다.</p>
<p><code>[id]</code> 이런 식으로 동작한다.
<img src="https://velog.velcdn.com/images/angel_eugnen/post/e988d2f7-dfdb-4000-930a-3507bdfab11f/image.png" alt=""></p>
<pre><code class="language-js">export default function MovieDetail(props){
    console.log(props)
    return &lt;div&gt;Movie&lt;/div&gt;
}</code></pre>
<p>콘솔로 찍어보면
<img src="https://velog.velcdn.com/images/angel_eugnen/post/2bcdd554-1d66-45f3-9c25-fd3d178352a0/image.png" alt=""></p>
<p>이렇게 나온다.</p>
<p>id, sesarchParams을 얻게 되는데, searchParams는 url 뒤에 <code>?</code> 데 대한 내용이다..</p>
<p>15 버전 이상 부터는 params 와 searchParams가 비동기로 작동해서, async await를  사용해야 한다.</p>
<pre><code class="language-js">const Page = async ({
  params,
  searchParams,
}: {
  params: Promise&lt;{ id: string }&gt;
  searchParams: Promise&lt;{ [key: string]: string }&gt;
}) =&gt; {
  const { id } = await params
  const search = await searchParams

  console.log(id, search)

  return &lt;h1&gt;My Post: {id}&lt;/h1&gt;
}

export default Page
</code></pre>
<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/c1d4986f-942a-41ff-9ce9-a862265cf1f1/image.png" alt=""></p>
<p>그러면 이렇게 출력된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Metadata]]></title>
            <link>https://velog.io/@angel_eugnen/Metadata</link>
            <guid>https://velog.io/@angel_eugnen/Metadata</guid>
            <pubDate>Thu, 10 Jul 2025 02:23:17 GMT</pubDate>
            <description><![CDATA[<h2 id="route-groups">route groups</h2>
<blockquote>
<p>layout, not-found 는 꼭 루트 폴더에 있어야 한다.</p>
</blockquote>
<p>그룹을 만들고 싶으면 <code>(home)</code> 처럼 폴더 이름을 괄호로 묶어줘야 한다.
폴더 이름을 지정해주면 url이 바뀌지 않고, 프레임 워크에서만 보인다.</p>
<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/c072afe3-38a5-41d2-a775-c2aa589b5e20/image.png" alt="">
<img src="https://velog.velcdn.com/images/angel_eugnen/post/9fa0e3e9-e65c-4ffd-a007-c8fe7bb3db00/image.png" alt=""></p>
<p>그래서 루트에 있던 홈 컴포넌트를 (home) 폴더로 옮길 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/559cc9db-7f85-46f0-b349-e8f66d792bd3/image.png" alt=""></p>
<h2 id="metadata">Metadata</h2>
<p>꼭 내보내야 하는 object 이고, 이것을 Metadata라고 부른다. 그리고 이것은 헤더에 표시된다. </p>
<pre><code class="language-bash">export const metadata = {
  title: &#39;Home&#39;,
  description: &#39;집에 보내줘 빨리, 바이 짜이쩬~&#39;,
}
export default function HOME(){
   return  &lt;div&gt; HOME&lt;/div&gt;
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/54d28e9b-5101-4eb0-aa7c-122a4c177265/image.png" alt=""></p>
<p>metadata는 병합된다.
title을 상위에 적고 하위에 description을 적으면
title은 상위에 있는 내용으로, description은 하위에 있는 내용으로 메타데이터가 병합된다. 또한 metadata는 서버 컴포넌트에만 있을 수 있다.</p>
<p>메타데이터에 대한 템플릿 또한 만들 수 있다.</p>
<hr>
<p>루트에서 템플릿 메타데이터를 만들었다.</p>
<pre><code class="language-bash">import { Metadata } from &quot;next&quot;

export const metadata :Metadata = {
  title: {template:&quot;%s | Next Movies&quot;,
  default: &quot;Loading...&quot;,
  },
  description: &#39;집에 보내줘 빨리, 바이 짜이쩬~&#39;,
}

export default function Layout({childeren}:{childeren:React.ReactNode}){
   return ( &lt;div&gt;
    {childeren}
&amp;copy; Next JS is great!    &lt;/div&gt;)
}

</code></pre>
<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/ab182a7e-3b46-489d-961f-aa66c2358985/image.png" alt=""></p>
<p>헤더에 반영되고, head 태그에 반영된 것을 알 수 있다.</p>
<hr>
<p>추가) 동적 메타데이터 정보가 api에 있을때? generateMetadata에서 호출해도 된다</p>
<pre><code class="language-js">
export async function generateMetadata({ params: { id } }: IParamas) {
  const movie = await getMovie(id); // 영화 정보를 불러오기 위해 API 를 부르면 안좋은가? -&gt; 최신버전은 fetch한번하면 캐싱된 응답을 받아서 괜찮다 ㅎㅎ
  return {
    title: movie.title,
  };
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Navigation]]></title>
            <link>https://velog.io/@angel_eugnen/Navigation</link>
            <guid>https://velog.io/@angel_eugnen/Navigation</guid>
            <pubDate>Thu, 10 Jul 2025 02:00:49 GMT</pubDate>
            <description><![CDATA[<h2 id="navigation">Navigation</h2>
<p>layout.tsx 라는 파일을 만들면
그 하위에 적용된다.</p>
<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/15f36479-8bb6-4ab6-b9e6-259b33ee19e2/image.png" alt=""></p>
<p>이렇게 있는 경우 루트의 layout 에서 컴포넌트를 임포트 하여 그것이 제일 상단으로, 그리고 아채 children이 추가된다.
<img src="https://velog.velcdn.com/images/angel_eugnen/post/0548bb81-84d7-4581-b4a8-fccd08ad2940/image.png" alt=""></p>
<p>그리고 about-us라는 루트 하위의 폴더에서 layout을 만들고 하위 url로 접속하면
<img src="https://velog.velcdn.com/images/angel_eugnen/post/a6a5c617-c17e-4f6c-a595-47caf7cb84b2/image.png" alt="">
<img src="https://velog.velcdn.com/images/angel_eugnen/post/b1e9d37d-f4f9-462e-afbc-314eadf8d94f/image.png" alt=""></p>
<p>app/about-us/jobs/sales 라는 폴더에서 레이아웃이 렌더링 되는 것을 알 수 있다.</p>
<p>레이아웃은 서로 상쇄되는 것이 아닌 중첩되고 있다.</p>
<h2 id="next-js-동작-방식">Next JS 동작 방식</h2>
<p><code>&lt;RootLayout/&gt;</code> 확인 -&gt; <code>/about-us</code> 경로 확인 -&gt; <code>&lt;Layout/&gt;</code> 확인 -&gt; 하위의 children인 <code>&lt;Sales/&gt;</code> 렌더링</p>
<p>즉 2개의 layout을 중첩하여 렌더링 하고 있다.</p>
<p>프레임 워크는 page의 가장 가까운 layout을 찾으려고 한다. 그래서 가까운 layout을 찾으면, 그 위 상위 항목을 확인하고 또 layout을 찾는다.</p>
<p>0</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hydration]]></title>
            <link>https://velog.io/@angel_eugnen/Hydration</link>
            <guid>https://velog.io/@angel_eugnen/Hydration</guid>
            <pubDate>Wed, 09 Jul 2025 06:21:08 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/5127ab46-da6b-4b48-9a53-70bc971ec296/image.png" alt=""></p>
<h1 id="hydration">Hydration</h1>
<p>단순 HTML을 React applaction으로 초기화 하는 작업이다.(HTML 위에서 React Application을 실행한다는 의미)</p>
<p>예를 들어 React 를 Initialize하여 onClick를 부착해 여기서 작성 가능 한 기능을 수행하도록 하는 것이다.</p>
<p>/aout-us 접근 =&gt; <code>&lt;button&gt;0&lt;/button&gt;</code> =&gt; 사용자 확인 이후 =&gt; <code>&lt;button onClick={()=&gt;이벤트수행}&gt;&lt;/button&gt;</code></p>
<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/43634903-7291-4552-89ac-f2c67871387a/image.png" alt="">
마치 위의 상태로 있는 것이다.
그리고 먼저 렌더링 한 후에 뒷단에서 next.js를 로딩하고 framework 를 초기화 하고 React application을 생성하는 것이다.</p>
<p>그러면 버튼이 interactive해지고 eventListener가 생기는 것이다.</p>
<hr>
<h3 id="🤔-render">🤔 render()</h3>
<p><code>ReactDOM.render(element, container, [callback])</code></p>
<ul>
<li>element: 화면에 그려진 React element (집어넣어 줄 요소)</li>
<li>container: React element를 해당 container DOM에 렌더링 (구체적인 위치)</li>
<li>callback: 렌더링 후 반환되는 값을 돌려주는 콜백 함수
CRA하게 되면 index.js에 다음 코드를 쉽게 볼 수 있습니다.</li>
</ul>
<pre><code class="language-js">import App from &#39;./App&#39;;

ReactDOM.render(&lt;App /&gt;, document.getElementById(&#39;root&#39;));</code></pre>
<p>즉, <code>&lt;App/&gt;</code> 컴포넌트를 root라는 id를 가지고 있는 엘리먼트 내부로 넣어주어 페이지를 렌더링 해주고 있습니다.</p>
<h3 id="🤔-hyrate">🤔 hyrate()</h3>
<p><code>ReactDOM.hydrate(element, container, [callback])</code>
기본적으로 render()와 동일하지만, ReactDOMServer로 렌더링된 HTML에 이벤트 리스터(자바스크립트 코드)를 연결해주기 위해 사용됩니다.</p>
<p>서버 사이드를 통해 이미 HTML에는 엘리먼트들이 채워져 있죠? 따라서 다시 render 해줄 필요 없이 hydrate를 통해 기존 마크업에 이벤트 리스너를 붙여주는 과정입니다.</p>
<p>위 과정들을 정리해봅시다.</p>
<p>Next.js는 서버에서 HTML을 문자열로 가져온 후에, 클라이언트에서 서버로부터 보내준 HTML을 render(), hydrate()하여 브라우저에 렌더링 했습니다. 이 일련의 과정을 Hydration이라고 합니다!</p>
<blockquote>
<p>Hydration 사전적 정의
[명사] 수분 공급</p>
</blockquote>
<p>서버의 데이터가 클라이언트의 DOM과 결합하는 과정을 빗대어 hydrate라는 단어로 정의된 것 같습니다.</p>
<p>React는 클라이언트 렌더링만 있어, 유저에게 보여줄 HTML, CSS 그리고 자바스크립트 모두 render() 함수를 이용해 생성하여, 모든 리소스를 한번에 렌더링합니다.</p>
<p>반면, Next.js는 서버에서 보여줄 HTML 컨텐츠를 미리 렌더링(내용을 채워서)하여 가져오기 때문에 render() 함수로 HTML 뼈대만 렌더하고, hydrate()를 통해 서버에서 받아온 HTML에 유저가 상호작용할 수 있는 이벤트 리스너(JS파일)을 연결하는 것입니다.</p>
<p>HTML에 JS파일(수분💦)을 주입한다고 해서 hydrate라고 이해할 수 있겠습니다!</p>
<hr>
<h2 id="client">client</h2>
<p>client 에서 hydrdate 되어 interactive 하게 만들어질 components는 오직 <code>use client</code> 지시어를 최 상단에 갖고있는 컴포넌트만 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/6a493a09-db15-439f-b278-87df46a8ba11/image.png" alt="">
<img src="blob:https://velog.io/b17bf315-df6f-4083-ab5b-f3738c17bab0" alt="업로드중.."></p>
<p>따라서 Navigation 만 hydration 되는 것이다.</p>
<blockquote>
<p>백엔드에서 render되고 프론트엔드에서 hydrate된다는 것이다.</p>
</blockquote>
<p>이는 data fetch 되는 방식에서 중요한 개념이다.
useQuery, useEffect ... 등등은 이제 안 쓸것이다.</p>
<h2 id="hydrate장점">hydrate장점</h2>
<p>: Javascript를 선택적으로 작은파일만 다운로드 하게 할 수 있다. 그래서 백엔드에서 HTML을 먼저 preRender할 수 있다. 그리고 use client를 작성하면 그 child는 모두 client component가 된다. 그리고 server component 에서 fetch를 진행하면, 보안을 신경쓰지 않아도 된다. client에서 render 되기 때문! 그래서 API키를 넣거나 DB에 접근해도 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Routing]]></title>
            <link>https://velog.io/@angel_eugnen/Routing</link>
            <guid>https://velog.io/@angel_eugnen/Routing</guid>
            <pubDate>Wed, 09 Jul 2025 05:57:46 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/c0d1a82e-fdd3-482d-b02e-86ef6de00102/image.png" alt=""></p>
<h2 id="recap">Recap</h2>
<p>Routing, Navigation, Layout, client, server components 등에 대해 배울 예정이다.</p>
<p>client, server 는 NextJS에서 나온 개념이라고 한다.</p>
<hr>
<p>NextJS는 프레임워크라서, 올바른 폴더에 정확한 이름의 파일이 들어가 있으면 NextJS는 우리 코드를 이용해서 만들어 준다.</p>
<p>app 폴더 안에 page 파일을 찾는것 처럼.
그리고 꼭 export를 해야한다.
그리고 동시에 layout을 찾는다. 그래서 자동 생성된다. meta data라는 객체와 RootLauout는 html 바디 안에 body 태그를 갖는다.</p>
<h2 id="routing">Routing</h2>
<ul>
<li><p>react 라우팅 방식</p>
<pre><code>/ -&gt; &lt;Home /&gt;
/about-us -&gt; &lt;AboutUs /&gt;</code></pre></li>
<li><p>NextJS 방식</p>
<pre><code>app 폴더 -&gt; / (roote segment)
app폴더/about-us 폴더 -&gt; /about-us</code></pre><p><img src="https://velog.velcdn.com/images/angel_eugnen/post/6b45f2db-2fb5-4252-a0a3-a3e4ac55de80/image.png" alt=""></p>
</li>
<li><p>주의
<img src="https://velog.velcdn.com/images/angel_eugnen/post/6f8d5606-a876-48c0-87c6-ca45bc9c6a44/image.png" alt=""></p>
</li>
</ul>
<p>이렇게 된 경우 url 에서 /about-us/company에 접근하면 404 와 같다. page.tsx파일이 할당되지 않았기 때문이다.</p>
<h2 id="navigation-bar">Navigation Bar</h2>
<pre><code class="language-js">&quot;use client&quot;
import Link from &quot;next/link&quot;
import { usePathname } from &quot;next/navigation&quot;

export default function Navigation(){
    const path = usePathname();
    return &lt;nav&gt;
        &lt;ul&gt;
            &lt;li&gt;
                &lt;Link href=&#39;/&#39;&gt;HOME&lt;/Link&gt;
                {path === &#39;/&#39; ? &#39;💕&#39;:&#39;&#39;}
            &lt;/li&gt;
            &lt;li&gt;
                &lt;Link href=&#39;/about-us&#39;&gt;AboutUS&lt;/Link&gt;
                {path === &#39;/about-us&#39; ? &#39;💕&#39;:&#39;&#39;}

            &lt;/li&gt;
        &lt;/ul&gt;
    &lt;/nav&gt;
}</code></pre>
<p>Link 라는 태그를 이용해 네비게이션을 구현한다.
현재 경로를 알기 위해선 usePathname을 이용한다. 이때 최상단에 <code>use client</code> 를 작성하지 않으면 에러가 난다.</p>
<p>왜그럴까? client component가 뭘까</p>
<p>평범한 react 가 렌더링 되는 방식은 client side application이다. 유저가 페이지에 도착한 시점에는 javascript를 모두 실행한 후 페이지를 렌더링 한다. html에 UI가 없는 시점에서 Javascript를 기다릴 때 빈화면이 보이게 된다. App을 실행하려면 JavaScript가 실행되어야만 한다.</p>
<p>client side rendering 단점</p>
<ul>
<li>만약 사용자가 데이터 연결 상태가 안좋은 상태에서 이 페이지에 접근하게 된다면, 아무 UI가 없는 빈 화면을 더 오래보게 될 것이다.</li>
<li>Google에서도 빈 HTML을 보게 된다.</li>
</ul>
<p>server side rendering 장점</p>
<ul>
<li>페이지의 내용들이 (html) 이미 브라우저에 있다. html 을 보여주는데 Javascript가 필요하지 않기 때문이다. 그래서 빈 화면을 보지 않게 된다.</li>
<li>가장 먼저 back end 에서 렌더링 되는 것이다.=&gt; 렌더링이란, JavaScript function을 가져와서 브라우저가 이해할 수 있는 html로 변환하는 작업니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Intro - NextJS]]></title>
            <link>https://velog.io/@angel_eugnen/Intro-NextJS</link>
            <guid>https://velog.io/@angel_eugnen/Intro-NextJS</guid>
            <pubDate>Wed, 09 Jul 2025 05:17:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/7cc77e9f-9896-44e9-88e2-6ec805732121/image.png" alt=""></p>
<h2 id="1-introduction">1. Introduction</h2>
<p>이 Framework 는 풀스택 웹앱을 개발하기 위한 최선의 Framework 라고 한다. 이 코스가 끝날 때에는 모든 프로그램을 NextJS로 만들고 싶어질거라 한다.</p>
<p>React Framework 중에 1등이라 하는 NextJS
최신 업데이트로 많이 발전했다고 한다.(14기준)
자동화로 개발자 경험 1위 라는데.. 기대가 된다.</p>
<h2 id="2-requirements">2. Requirements</h2>
<blockquote>
<p>React.js 다룰줄 알아야 한다. (state, props, fetch, routing 등을 알아야 하니..)</p>
</blockquote>
<h2 id="3-libraryreact-vs-frameworknextjs">3. Library(React) vs Framework(NextJS)</h2>
<ul>
<li><p>Library: 라이브러리는 코드 내에서 사용하는 것이다. 원하는 아키텍를 원하는 방식으로 짠다. 함수 지향, 객체지향 프로그래밍이 가능하다. 코드 내에서 사용하고 사용의 주체는, 사용자이다. 파일이름, 폴더 구조 등. 라이브러리는 우리가 사용할 때 쓸 수 있다는 것이다.라이브러리의 도움이 필요할 때만 가져와서 사용할 수 있다. 다운받아서 사용하는 것. React는 UI 인터페이스를 build하는데 사용하는 라이브러리 이다.반응형 인터페이스를 구축하는데 도움이 된다. CSS 에 Styled Components 를 사용하거나 tailwind를 사용하거나, expo 를 사용하거나 routing을 사용하거나.. 등등의 모든 자유를 개발자가 가지는 것이다.</p>
</li>
<li><p>Framework: 개발자에게 주도권은 없다. 프레임워크가 주도하고 담당한다. 프레임워크가 대신 자동화 하고 결정을 담당한다. 그래서 NextJS는 많은 Feature을 가지고 있다. 그래서 규칙을 따라야 한다. 이것이 프레임 워크다. 개발자가 규칙을 지켜야 한다는 것이다. 적당한 폴더에 파일을 만들어야 한다는 것. 그것이 지켜지지 않는다면 동작하지 않는다. import의 개념을 잊어라. 올바른 변수의 모양으로 넣어야 한다는 것이다. 올바른 위치에 넣으면 그것을 실행해 줄것이다.</p>
</li>
</ul>
<blockquote>
<p>라이브러리는 개발자가 사용하고, 프레임워크는 코드를 사용한다.</p>
</blockquote>
<h2 id="4-old-vs-new-version">4. Old vs New Version</h2>
<ul>
<li>Page Router : app 폴더에서 찾는다</li>
<li>App Router : pages폴더에서 찾는다.</li>
</ul>
<p>app에서 라우팅 하는 방식과 data fetching하는 법은 많이 바뀌었다.
getStaticProps같은 것은 잊어라. page Router를 사용했다면, 최신 버전을 사용하기 위해선 migration 해야 한다.</p>
<h2 id="5-installation">5. Installation</h2>
<p><code>npm init -y</code></p>
<pre><code class="language-js">// 이후  package.json 생김. author 수정
{
  &quot;name&quot;: &quot;next-practice&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;
  },
  &quot;repository&quot;: {
    &quot;type&quot;: &quot;git&quot;,
    &quot;url&quot;: &quot;git+https://github.com/Eugenius1st/next-practice.git&quot;
  },
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;MIT&quot;, // 여기 수정
  &quot;bugs&quot;: {
    &quot;url&quot;: &quot;https://github.com/Eugenius1st/next-practice/issues&quot;
  },
  &quot;homepage&quot;: &quot;https://github.com/Eugenius1st/next-practice#readme&quot;,
  &quot;description&quot;: &quot;&quot;
}
</code></pre>
<p><code>npm install react@latest next@latest react-dom@latest</code></p>
<ul>
<li>react: UI 와 여타 많은 것들을 구성하는 부분</li>
<li>reactdom: 브라우저에서 렌더링 하는 도구</li>
</ul>
<pre><code class="language-js">// 이후 스크립트 수정
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;next dev&quot;

  },</code></pre>
<p>실행하면 NextJS는 pages라는 폴더를 찾으로 할 것이다. 그리고 그 폴더는 app 폴더 안에 있을 것이다.</p>
<p>다음
app/page.tsx 혹은 .jsx를 만들어라.
<img src="https://velog.velcdn.com/images/angel_eugnen/post/9a959907-fdee-4585-b985-d4b3b761f1cc/image.png" alt=""></p>
<p>그리고 
<code>npm run dev</code>로 실행</p>
<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/ffe51d41-792f-4d26-a3a3-fc662ea4d80b/image.png" alt=""></p>
<p>그리고 실행하면 local:3000 에서 실행중임을 알 수 있다.
그리고 자연히 layout.tsx 가 생겨남을 알 수 있다.</p>
<pre><code class="language-js">export const metadata = {
  title: &#39;Next.js&#39;,
  description: &#39;Generated by Next.js&#39;,
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    &lt;html lang=&quot;en&quot;&gt;
      &lt;body&gt;{children}&lt;/body&gt;
    &lt;/html&gt;
  )
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker 설치(프론트엔드, React, TypeScript)]]></title>
            <link>https://velog.io/@angel_eugnen/Docker-%EC%84%A4%EC%B9%98%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-React-TypeScript</link>
            <guid>https://velog.io/@angel_eugnen/Docker-%EC%84%A4%EC%B9%98%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-React-TypeScript</guid>
            <pubDate>Wed, 27 Nov 2024 04:46:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/5951c598-0d13-4011-b7d8-a15e2ad0daff/image.png" alt="">
도커는 개발 환경을 표준화하고 컨테이너를 통해 손쉽게 애플리케이션을 실행할 수 있는 도구입니다. React와 TypeScript로 개발하는 프론트엔드 프로젝트에 도커를 적용하려면 다음 단계를 따라 진행할 수 있습니다.</p>
<h1 id="1-도커-설치">1. 도커 설치</h1>
<p>도커를 아직 설치 중이라면, Docker 공식 사이트에서 OS에 맞는 설치 파일을 다운로드하여 설치합니다. 설치 후 Docker Desktop을 실행하고 정상적으로 작동하는지 확인합니다.</p>
<blockquote>
<p><a href="https://www.docker.com/">https://www.docker.com/</a></p>
</blockquote>
<h2 id="2-react--typescript-프로젝트를-위한-dockerfile-생성">2. React + TypeScript 프로젝트를 위한 Dockerfile 생성</h2>
<p>프로젝트 루트 디렉토리에 Dockerfile을 생성합니다.</p>
<pre><code># Node.js 이미지를 베이스로 사용
FROM node:18-alpine

# 컨테이너 내 앱 디렉토리 생성
WORKDIR /usr/src/app

# package.json과 package-lock.json 복사
COPY package*.json ./

# 의존성 설치
RUN npm install

# 애플리케이션 소스 코드 복사
COPY . .

# 개발 서버 실행
CMD [&quot;npm&quot;, &quot;start&quot;]

# 컨테이너가 노출할 포트
EXPOSE 3000</code></pre><h2 id="3-docker-composeyml-파일-생성">3. docker-compose.yml 파일 생성</h2>
<p>개발 시 여러 컨테이너를 관리하기 편하도록 docker-compose.yml 파일을 작성합니다.</p>
<pre><code>version: &#39;3.9&#39;
services:
  frontend:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - &quot;3000:3000&quot; # 호스트의 3000 포트를 컨테이너의 3000 포트에 매핑
    volumes:
      - .:/usr/src/app # 로컬 파일 변경 시 컨테이너에 반영
      - /usr/src/app/node_modules # node_modules는 볼륨 매핑 제외
    stdin_open: true
    environment:
      - CHOKIDAR_USEPOLLING=true # 파일 변경 감지를 위한 설정
4. 프로젝트의 package.json 스크립트 수정
React 프로젝트가 도커 컨테이너 내에서도 제대로 실행되도록 설정을 추가합니다. 예를 들어, start 스크립트를 수정합니다.

&quot;scripts&quot;: {
  &quot;start&quot;: &quot;react-scripts start&quot;,
  &quot;build&quot;: &quot;react-scripts build&quot;,
  &quot;test&quot;: &quot;react-scripts test&quot;,
  &quot;eject&quot;: &quot;react-scripts eject&quot;
}</code></pre><h2 id="5-도커-빌드-및-실행">5. 도커 빌드 및 실행</h2>
<p>도커 환경을 실행하려면 다음 명령어를 사용합니다.</p>
<blockquote>
<h3 id="빌드">빌드</h3>
<p><code>docker-compose build</code></p>
</blockquote>
<blockquote>
<h3 id="실행">실행</h3>
<p><code>docker-compose up</code></p>
</blockquote>
<h2 id="6-도커-환경-확인">6. 도커 환경 확인</h2>
<p>브라우저에서 <a href="http://localhost:3000%EC%9C%BC%EB%A1%9C">http://localhost:3000으로</a> 접속하여 React 앱이 정상적으로 실행되는지 확인합니다.
코드 수정 시 변경 사항이 자동으로 반영되는지 확인합니다.</p>
<h2 id="7-추가-고려-사항">7. 추가 고려 사항</h2>
<pre><code>환경 변수: .env 파일을 사용하고, docker-compose.yml에서 이를 참조하도록 설정합니다.</code></pre><p>생산 환경 설정: Dockerfile에 RUN npm run build를 추가하고, Nginx 또는 다른 웹 서버를 통해 빌드된 파일을 서빙할 수 있도록 설정합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker-도커 허브]]></title>
            <link>https://velog.io/@angel_eugnen/Docker-%EB%8F%84%EC%BB%A4-%ED%97%88%EB%B8%8C</link>
            <guid>https://velog.io/@angel_eugnen/Docker-%EB%8F%84%EC%BB%A4-%ED%97%88%EB%B8%8C</guid>
            <pubDate>Wed, 27 Nov 2024 04:42:53 GMT</pubDate>
            <description><![CDATA[<h1 id="docker-도커-허브">Docker-도커 허브</h1>
<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/287f5837-3282-46c5-b1f3-d10fdbd7c7d4/image.png" alt=""></p>
<p>도커 허브 ID와 비밀번호는 도커 이미지를 <strong>도커 허브(Docker Hub)</strong>에서 <strong>푸시(push)</strong>하거나 풀(pull) 할 때 사용됩니다. 도커 허브는 도커 이미지들을 저장하고 관리하는 레지스트리 서비스입니다. 즉, 특정 이미지를 다운로드하거나, 자신이 만든 이미지를 도커 허브에 업로드하기 위해 사용될 수 있습니다.</p>
<h2 id="1-도커-허브-로그인">1. 도커 허브 로그인</h2>
<p>도커 허브에 로그인하려면 터미널에서 아래 명령어를 실행해야 합니다. 이때, 백엔드에서 제공한 도커 허브 ID와 PW를 사용합니다.</p>
<pre><code>docker login
명령어를 입력하면 아래와 같은 프롬프트가 뜨고, 여기에 ID와 비밀번호를 입력합니다.

Username: your-docker-hub-id
Password: your-docker-hub-password</code></pre><blockquote>
<p>로그인이 성공하면, &quot;Login Succeeded&quot; 메시지가 나타납니다.</p>
</blockquote>
<h2 id="2-이미지-pull-다운로드">2. 이미지 Pull (다운로드)</h2>
<p>도커 허브에서 이미지를 받아오는(pull) 방법입니다. 백엔드에서 제공한 도커 허브 ID와 이미지 이름을 알고 있다면, 해당 이미지를 아래와 같이 다운로드할 수 있습니다.</p>
<pre><code>docker pull your-docker-hub-id/your-image-name:tag
예를 들어, 백엔드에서 제공한 이미지 이름이 ai-soccer-backend이고 태그가 latest라면 다음과 같이 실행합니다.

docker pull your-docker-hub-id/ai-soccer-backend:latest</code></pre><blockquote>
<p>이 명령어는 도커 허브에서 해당 이미지를 다운로드하여 로컬 머신에 저장합니다.</p>
</blockquote>
<h2 id="3-이미지-push-업로드">3. 이미지 Push (업로드)</h2>
<p>만약 자신이 만든 도커 이미지를 도커 허브에 업로드하고 싶다면, 먼저 이미지를 빌드한 후 도커 허브에 푸시(push)할 수 있습니다.</p>
<pre><code class="language-이미지">
docker tag my-image your-docker-hub-id/my-image:latest
이미지 푸시: 태그가 완료되면 이미지를 도커 허브에 푸시할 수 있습니다.

docker push your-docker-hub-id/my-image:latest</code></pre>
<blockquote>
<p>이렇게 하면 your-docker-hub-id/my-image:latest라는 이름으로 도커 허브에 이미지가 업로드됩니다.</p>
</blockquote>
<h2 id="4-도커-컴포즈에서-이미지-사용하기">4. 도커 컴포즈에서 이미지 사용하기</h2>
<p>만약 docker-compose.yml 파일을 사용해서 도커 이미지를 실행하고 있다면, image 키를 사용하여 도커 허브에서 이미지를 가져올 수 있습니다.</p>
<pre><code>예를 들어:

version: &#39;3.9&#39;
services:
  backend:
    image: your-docker-hub-id/ai-soccer-backend:latest
    ports:
      - &quot;8080:8080&quot;</code></pre><blockquote>
<p>이렇게 설정하면 docker-compose up 명령어를 실행할 때 도커 컴포즈가 자동으로 도커 허브에서 이미지를 풀(pull)하여 컨테이너를 실행합니다.</p>
</blockquote>
<hr>
<h2 id="요약">요약</h2>
<ol>
<li>도커 허브 로그인: docker login 명령어를 사용하여 제공된 ID와 비밀번호로 로그인.</li>
<li>이미지 Pull: docker pull 명령어를 사용하여 도커 허브에서 이미지를 다운로드.</li>
<li>이미지 Push: 이미지를 도커 허브에 업로드하려면 docker tag와 docker push 명령어를 사용.</li>
<li>도커 컴포즈 설정: docker-compose.yml에서 image 키로 도커 허브의 이미지를 사용할 수 있습니다.</li>
<li>이렇게 제공된 도커 허브 ID와 비밀번호를 사용하여 필요한 이미지를 다운로드하거나 업로드할 수 있습니다. </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python_OOP(객체지향 프로그래밍)]]></title>
            <link>https://velog.io/@angel_eugnen/PythonOOP%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@angel_eugnen/PythonOOP%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Sun, 24 Nov 2024 14:08:40 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/6b411397-442d-45cc-af58-8c972c4dcb78/image.png" alt=""></p>
<h1 id="oopobject-oriented-programming-객체-지향-프로그래밍란">OOP(Object-Oriented Programming, 객체 지향 프로그래밍)란?</h1>
<p>OOP는 코드를 구성하는 방법에 대한 규칙
OOP는 직관적이고 데이터아 구조를 직관적으로 생각하게 해준다.
Class 에는 속성, 메서드, 상속이 있다.</p>
<hr>
<h2 id="oop가-필요한-이유">OOP가 필요한 이유?</h2>
<ul>
<li>이해하기 쉬운 코드를 위해.</li>
<li>캡슐화를 위해</li>
<li>다른 종류의 데이터와 함수들을 동일한 하나의 파일에 갖고 있는것 대신에, 데이터를 구체화 할 수 있다. (Box, Object, Bubble)</li>
<li>더 나은 방법으로 이해하기 쉽고, 보다 전문적으로 개발할 수 있다는 큰 장점을 얻을 수 있습니다.</li>
</ul>
<p>OOP 는 확장가능성 있도록만든다. OOP 는 데이터를 수정하기 위해 어떤 함수를 넣어야 하는지를 알려준다. 명확한 경게를 만드는 것이다. 많은 딕셔너리로 다른 일을 하는 함수를 만들 필요가 없다.</p>
<hr>
<h2 id="class">Class</h2>
<p>class 는 구조를 정의할 수 있도록 해준다.
Class 는 캡슐화 하도록 도와준다. 또한 이 데이터를 기반으로 동작하는 함수를 정의할 수 있다.</p>
<pre><code class="language-py">class Puppy:
  pass

ruffus = Puppy() # ruffus 는 Puppy의 한 종류라는 뜻이다.

print(ruffus) 
# &lt;__main__.Puppy object at 0x0000028B34AFE470&gt;</code></pre>
<hr>
<h2 id="method">Method</h2>
<p>method는 class 안에 있는 함수이다. 그게 다다.
함수를 만들고 class 안에 넣기만 하면 끝.
즉, class 밖에 있는 것은 function(함수) 인 것이고 class 안에 있는 것은 method(메소드) 인 것이다.</p>
<pre><code class="language-py">class Puppy:
  def __init__():
    print(&quot;Puppy is born&quot;)

# TypeError: Puppy.__init__() takes 0 positional arguments but 1 was given
# 0 개의 arguments 를 가져야 하는데 1개의 arguments 가 온 것이다.
# method 를 가지고 있을 경우 자동으로 &quot;기본적으로&quot; method 의 첫번째 arguments 는 self 가 되어야 한다. 물론 self 라고 쓸 필요는 없다.</code></pre>
<p>ruffus = Puppy() # Puppy를 생성한 순간에 바로 <strong>init</strong> 함수가 자동적으로 호출 된 것이다.</p>
<pre><code class="language-py"># method 의 첫번째 arguments 는 self다.
# 자신을 참조한다.
class Puppy:
  def __init__(self):
    print(self)
    print(&quot;Puppy is born&quot;)

ruffus = Puppy() # Puppy를 생성한 순간에 바로 __init__ 함수가 실행 된 것이다.

print(ruffus)
# &lt;__main__.Puppy object at 0x0000022295EFED70&gt; 메모리 주소가 출력되었다.
# Puppy is born
# &lt;__main__.Puppy object at 0x0000022295EFED70&gt;</code></pre>
<p>이것이 왜 유용할까?
자기 자신을 커스텀 할 수 있기에 유용하다.</p>
<pre><code class="language-py">class Puppy:
  def __init__(self):
    self.name = &quot;ruffus&quot;
    self.age = 0.1
    self.breed = &#39;biggke&#39;
ruffus = Puppy() # Puppy를 생성한 순간에 바로 __init__ 함수가 실행 된 것이다.
print(ruffus.name, ruffus.age, ruffus.breed)
# ruffus 0.1 biggke</code></pre>
<p>다음 편에서 커스텀 하는 법을 더 알아보자 ~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[페이지 한번에 생성하는 맥 명령어]]></title>
            <link>https://velog.io/@angel_eugnen/%ED%8E%98%EC%9D%B4%EC%A7%80-%ED%95%9C%EB%B2%88%EC%97%90-%EC%83%9D%EC%84%B1%ED%95%98%EB%8A%94-%EB%A7%A5-%EB%AA%85%EB%A0%B9%EC%96%B4</link>
            <guid>https://velog.io/@angel_eugnen/%ED%8E%98%EC%9D%B4%EC%A7%80-%ED%95%9C%EB%B2%88%EC%97%90-%EC%83%9D%EC%84%B1%ED%95%98%EB%8A%94-%EB%A7%A5-%EB%AA%85%EB%A0%B9%EC%96%B4</guid>
            <pubDate>Mon, 18 Nov 2024 07:25:14 GMT</pubDate>
            <description><![CDATA[<h2 id="페이지-한번에-생성하는-맥-명령어">페이지 한번에 생성하는 맥 명령어</h2>
<p>아주 편하다!</p>
<pre><code class="language-js">mkdir -p src/{components/pages/{player,staff/{coach,supervision,office},systemAdmin,analyst,superAdmin,shared},routes,types,utils}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Expo Go]]></title>
            <link>https://velog.io/@angel_eugnen/Expo-Go</link>
            <guid>https://velog.io/@angel_eugnen/Expo-Go</guid>
            <pubDate>Mon, 18 Nov 2024 04:05:59 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/adf73bcb-83b5-49d1-85bc-5df900e6267e/image.png" alt=""></p>
<h2 id="배포">배포</h2>
<h3 id="처음-배포시">처음 배포시</h3>
<p>eas publish</p>
<h3 id="두번째부터">두번째부터</h3>
<p>eas update</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python_웹스크래퍼만들기]]></title>
            <link>https://velog.io/@angel_eugnen/Python%EC%9B%B9%EC%8A%A4%ED%81%AC%EB%9E%98%ED%8D%BC%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@angel_eugnen/Python%EC%9B%B9%EC%8A%A4%ED%81%AC%EB%9E%98%ED%8D%BC%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 10 Nov 2024 08:35:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/angel_eugnen/post/36cdc190-2f4a-401b-9425-ffc22a9613cd/image.png" alt=""></p>
<p>인스타그램 백엔드는 python 으로 만들어져 있다.
인공지능 역시 python 으로 만들어졌다. (model 학습 등등)
금융에서 trading 같은 것도 가능하다.
과학 분석, 통계 등등 데이터의 시각적 처리도 python으로 한다.
생태계가 정말 크고 다양하다.</p>
<p>python은 정말 많은 것들을 할 수 있다.
python 의 세계로 들어오면 다양한 것을 할 수 있다는 것이다.</p>
<hr>
<h3 id="간단-문법-짚고-넘어가기1">간단 문법 짚고 넘어가기1</h3>
<pre><code class="language-python">websites = (&quot;google.com&quot;, &quot;airbnb.com&quot;, &quot;https://twitter.com&quot;, &quot;facebook.com&quot;,
            &quot;https://tiktok.com&quot;)

for website in websites:
  if not website.startswith(&#39;https://&#39;):
     # 이게 바로 변수 넣는 법
    website = f&quot;https://{website}&quot;
    print(website)

# string 에 이렇게 변수를 넣을 수 있다.
</code></pre>
<h3 id="간단-문법-짚고-넘어가기2">간단 문법 짚고 넘어가기2</h3>
<p>아무것도 설치하지 않고 사용할 수 있는!!</p>
<blockquote>
<p>파이썬 내장 라이브러리: <a href="https://docs.python.org/3/library/index.html">https://docs.python.org/3/library/index.html</a></p>
</blockquote>
<h4 id="pypi">pypi</h4>
<p>거의 모든 300,000 개 이상의 프로젝트를 찾을 수 있다.</p>
<blockquote>
<p><a href="https://pypi.org/">https://pypi.org/</a></p>
</blockquote>
<p>그중에 하나를 사용해 보자
<img src="https://velog.velcdn.com/images/angel_eugnen/post/96c68949-a95e-4cc9-b4aa-72cbe9c2c5ae/image.png" alt=""></p>
<ul>
<li>requests
python 코드에서 웹사이트로 request 보내는 것을 할 수 있게 해주는 라이브러리 이다.</li>
</ul>
<pre><code class="language-python">from requests import get #ㅎget 은 function이다

websites = (&#39;google.com&#39;, &#39;airbnb.com&#39;, &#39;https://twitter.com&#39;)

for website in websites: #status 코드 확인
  if not website.startswith(&#39;https://&#39;):
    website = f&quot;https://{website}&quot;
    response = get(website)
    print(response.status_code)
 # 웹사이트가 성공적으로 응답함을 알 수 있다.</code></pre>
]]></description>
        </item>
    </channel>
</rss>