<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sh_yang.log</title>
        <link>https://velog.io/</link>
        <description>개발 한웅큼 메모 한 스푼</description>
        <lastBuildDate>Mon, 14 Apr 2025 15:04:57 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sh_yang.log</title>
            <url>https://velog.velcdn.com/images/sh_yang/profile/0e9be794-357f-419d-8327-41920916637e/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sh_yang.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sh_yang" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Typescript] Next.js + Typescript 초기 설정]]></title>
            <link>https://velog.io/@sh_yang/Typescript-Next.js-Typescript-%EC%B4%88%EA%B8%B0-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@sh_yang/Typescript-Next.js-Typescript-%EC%B4%88%EA%B8%B0-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Mon, 14 Apr 2025 15:04:57 GMT</pubDate>
            <description><![CDATA[<p><strong>Next.js 프로젝트에 TypeScript를 적용하면 더 안전하고 유지보수하기 쉬운 코드를 작성할 수 있으며,
이 포스트에서는 Next.js에서 TypeScript 환경을 처음 설정하는 방법을 단계별로 정리.</strong></p>
<h2 id="1-nextjs-프로젝트-생성">1. Next.js 프로젝트 생성</h2>
<pre><code class="language-bash">npx create-next-app@latest my-app
cd my-app</code></pre>
<blockquote>
<p>이미 프로젝트가 있다면 그 디렉토리로 이동.</p>
</blockquote>
<h2 id="2-typescript-패키지-설정">2. Typescript 패키지 설정</h2>
<pre><code class="language-bash">npm install --save-dev typescript @types/react @types/node</code></pre>
<blockquote>
<p>자동으로 TypeScript 설정 파일을 생성.
tsconfig.json은 처음 실행할 때 자동으로 생성됨.</p>
</blockquote>
<h2 id="3-파일-확장자-변경">3. 파일 확장자 변경</h2>
<ul>
<li>기존의 <code>.js</code>, <code>.jsx</code> 파일을 <code>.ts</code>, <code>.tsx</code>로 변경.</li>
</ul>
<p>예시:</p>
<ul>
<li>pages/index.js → pages/index.tsx</li>
<li>components/Button.jsx → components/Button.tsx</li>
</ul>
<blockquote>
<p>주의: <code>.tsx</code>는 JSX 문법이 들어간 파일에서만 사용.</p>
</blockquote>
<h2 id="4-tsconfigjson-자동-생성">4. tsconfig.json 자동 생성</h2>
<p>다음 명령어를 실행하면 Next.js가 자동으로 <code>tsconfig.json</code>을 생성:</p>
<pre><code class="language-bash">npm run dev</code></pre>
<blockquote>
<p>실행 시, tsconfig.json이 없다면 다음과 같이 자동 생성:</p>
</blockquote>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es5&quot;,
    &quot;lib&quot;: [&quot;dom&quot;, &quot;dom.iterable&quot;, &quot;esnext&quot;],
    &quot;allowJs&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;strict&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;module&quot;: &quot;esnext&quot;,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;resolveJsonModule&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;jsx&quot;: &quot;preserve&quot;
  },
  &quot;include&quot;: [&quot;next-env.d.ts&quot;, &quot;**/*.ts&quot;, &quot;**/*.tsx&quot;],
  &quot;exclude&quot;: [&quot;node_modules&quot;]
}</code></pre>
<p><code>strict</code> 모드를 꺼야 하는 경우에는 <code>&quot;strict&quot;: false</code>로 수정.</p>
<h2 id="5-타입-정의-파일-확인">5. 타입 정의 파일 확인</h2>
<p>Next.js는 자동으로 <code>next-env.d.ts</code> 파일을 생성:</p>
<pre><code class="language-ts">/// &lt;reference types=&quot;next&quot; /&gt;
/// &lt;reference types=&quot;next/types/global&quot; /&gt;
/// &lt;reference types=&quot;next/image-types/global&quot; /&gt;</code></pre>
<blockquote>
<p>이 파일은 삭제 금지, 타입 정의가 깨지면 컴파일 오류가 발생할 수 있음.</p>
</blockquote>
<h2 id="6-typescript-사용-예시">6. TypeScript 사용 예시</h2>
<blockquote>
<p>컴포넌트에서 type 정의</p>
</blockquote>
<pre><code class="language-tsx">type ButtonProps = {
  label: string;
  onClick?: () =&gt; void;
};

const Button = ({ label, onClick }: ButtonProps) =&gt; {
  return &lt;button onClick={onClick}&gt;{label}&lt;/button&gt;;
};</code></pre>
<h2 id="7-자주-사용하는-타입-패키지">7. 자주 사용하는 타입 패키지</h2>
<p><code>@types/react</code> : React 관련 타입
<code>@types/node</code> : Node.js 관련 타입
<code>@types/uuid</code>, <code>@types/lodash</code> 등: 서드파티 라이브러리 타입</p>
<h2 id="8-eslint와-함께-쓰기">8. ESLint와 함께 쓰기</h2>
<ul>
<li>코드 품질을 유지하려면 ESLint 설정도 같이 설정.</li>
</ul>
<pre><code class="language-bash">pm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin</code></pre>
<p><code>.eslintrc.json</code>설정 예시</p>
<pre><code>{
  &quot;parser&quot;: &quot;@typescript-eslint/parser&quot;,
  &quot;extends&quot;: [
    &quot;plugin:@typescript-eslint/recommended&quot;,
    &quot;next/core-web-vitals&quot;
  ]
}</code></pre><h2 id="9-절대경로-설정">9. 절대경로 설정</h2>
<p><code>tsconfig.json</code>에 다음 항목을 추가하면 <code>@/components/Button</code>처럼 경로를 쉽게 사용할 수 있음:</p>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;baseUrl&quot;: &quot;.&quot;,
    &quot;paths&quot;: {
      &quot;@/*&quot;: [&quot;./src/*&quot;]
    }
  }
}</code></pre>
<p><code>app</code> 폴더를 <code>src/app</code>으로 옮겨두면 디렉토리도 더 깔끔해짐.</p>
<h2 id="정리">정리</h2>
<p>이제 Next.js 프로젝트에서 TypeScript를 안전하게 사용할 준비 완료</p>
<p>정리하자면:</p>
<ul>
<li>타입 안정성 덕분에 코드 퀄리티가 상승</li>
<li>초기 설정만 잘 하면 유지보수는 훨씬 쉬움</li>
<li>협업 시 커뮤니케이션 비용 감소</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NextJS] App Router]]></title>
            <link>https://velog.io/@sh_yang/NextJS-App-Router</link>
            <guid>https://velog.io/@sh_yang/NextJS-App-Router</guid>
            <pubDate>Sat, 12 Apr 2025 19:12:53 GMT</pubDate>
            <description><![CDATA[<p>Next.js는 13버전부터 App Router를 도입하여 라우팅 방식을 대폭 개선.
기존의 페이지 단위 라우팅에서 더 세밀한 제어가 가능해졌고, 
서버 컴포넌트, 스트리밍 및 Suspense 등 최신 기술을 자연스럽게 사용할 수 있도록 설계.</p>
<p>이 포스트에서는 Next.js의 App Router를 처음 접하는 개발자를 위해, 
라우팅 구조와 각 파일(page.tsx, route.ts 등)의 명확한 역할 및 사용법을 알기 쉽게 정리.</p>
<h2 id="1-app-router란">1. App Router란?</h2>
<ul>
<li>기존의 Pages Router와의 차이점</li>
<li>장점: 서버 컴포넌트 지원, 중첩 라우팅, 병렬 라우팅 등</li>
</ul>
<h2 id="2-폴더-기본-구조">2. 폴더 기본 구조</h2>
<pre><code class="language-cpp">app/
├── layout.tsx
├── page.tsx
├── loading.tsx
├── error.tsx
├── template.tsx
└── api/
    └── route.ts</code></pre>
<ul>
<li>폴더와 파일의 구조적 의미 소개</li>
</ul>
<h2 id="3-파일별-라우팅-특성-상세-정리">3. 파일별 라우팅 특성 상세 정리</h2>
<h3 id="1-pagetsx-기본-페이지-컴포넌트">1. <code>page.tsx</code> (기본 페이지 컴포넌트)</h3>
<ul>
<li>페이지 단위의 UI를 렌더링하는 기본 컴포넌트</li>
<li>서버 및 클라이언트 컴포넌트로 구성 가능<ul>
<li><code>&#39;use client&#39;</code>키워드로 클라이언트 구분 가능</li>
</ul>
</li>
<li>페이지 진입점(entry-point) 역할</li>
<li>정적(Static) 및 동적(Dynamic) 라우팅 모두 지원</li>
</ul>
<pre><code class="language-tsx">const Page = () =&gt; {
  return (
      &lt;div&gt; Page &lt;/div&gt;  
  );
}

export default TestPage;</code></pre>
<h3 id="2-layouttsx-레이아웃-컴포넌트">2. <code>layout.tsx</code> (레이아웃 컴포넌트)</h3>
<ul>
<li>공통 레이아웃(헤더, 푸터 등)을 정의하고 페이지 간 상태를 유지</li>
<li>페이지 이동 시에도 레이아웃 유지</li>
<li>중첩된(Nested) 레이아웃 구조 가능</li>
</ul>
<pre><code class="language-tsx">const Layout= ({ children }) =&gt; {
  return (
    &lt;&gt;
      &lt;header&gt;헤더&lt;/header&gt;
      {children}
      &lt;footer&gt;푸터&lt;/footer&gt;
    &lt;/&gt;
  );
}

export default Layout;</code></pre>
<h3 id="3-routets-api-라우팅">3. <code>route.ts</code> (API 라우팅)</h3>
<ul>
<li>서버 기반 API 엔드포인트를 정의</li>
<li>HTTP 메소드(GET, POST, PUT, DELETE) 처리 방법</li>
<li>REST API 구현 시 필수</li>
</ul>
<pre><code class="language-ts">// app/api/users/route.ts

import { NextResponse } from &#39;next/server&#39;;

let users = [
  { id: 1, name: &#39;Alice&#39; },
  { id: 2, name: &#39;Bob&#39; }
];

// GET - 전체 유저 조회
export const GET = async () =&gt; {
  return NextResponse.json(users);
}

// POST - 새로운 유저 추가
export const POST = async (request: Request) =&gt; {
  const body = await request.json();
  const newUser = {
    id: users.length + 1,
    name: body.name
  };
  users.push(newUser);

  return NextResponse.json(newUser, { status: 201 });
}

// PUT - 유저 이름 수정 (간단히 id=1만 수정한다고 가정)
export const PUT (request: Request) =&gt; {
  const body = await request.json();
  const user = users.find(u =&gt; u.id === body.id);

  if (!user) {
    return NextResponse.json({ error: &#39;User not found&#39; }, { status: 404 });
  }

  user.name = body.name;
  return NextResponse.json(user);
}

// DELETE - 유저 삭제 (id 기반 삭제)
export const DELETE = async (request: Request) =&gt; {
  const { searchParams } = new URL(request.url);
  const id = parseInt(searchParams.get(&#39;id&#39;) || &#39;0&#39;);

  const index = users.findIndex(u =&gt; u.id === id);
  if (index === -1) {
    return NextResponse.json({ error: &#39;User not found&#39; }, { status: 404 });
  }

  const deleted = users.splice(index, 1)[0];
  return NextResponse.json(deleted);
}</code></pre>
<h3 id="4-loadingtsx-로딩-컴포넌트">4. <code>loading.tsx</code> (로딩 컴포넌트)</h3>
<ul>
<li>페이지 데이터를 로딩 중일 때 사용자에게 표시할 UI를 구성</li>
<li>Suspense와 스트리밍 데이터 처리 시 자동으로 렌더링</li>
<li>별도의 로딩 처리 로직이 필요하지 않음</li>
</ul>
<pre><code class="language-tsx">const Loading = () =&gt; {
  return &lt;div&gt;Loading...&lt;/div&gt; 
}

export default Loading;</code></pre>
<ul>
<li>VS <code>layout.tsx</code> vs <code>template.tsx</code></li>
</ul>
<table>
<thead>
<tr>
<th>항목</th>
<th><code>layout.tsx</code></th>
<th><code>template.tsx</code></th>
</tr>
</thead>
<tbody><tr>
<td><strong>위치</strong></td>
<td><code>app/경로/layout.tsx</code></td>
<td><code>app/경로/template.tsx</code></td>
</tr>
<tr>
<td><strong>재사용 여부</strong></td>
<td>유지됨 (경로 변경해도 유지됨)</td>
<td>유지 안 됨 (경로 변경 시 새로 마운트됨)</td>
</tr>
<tr>
<td><strong>용도</strong></td>
<td>공통 UI 구성, 상태 유지</td>
<td>페이지 전환 효과, 초기화 목적</td>
</tr>
<tr>
<td><strong>적용 대상</strong></td>
<td>하위 모든 <code>page.tsx</code>, <code>layout.tsx</code> 등</td>
<td>하위 <code>page.tsx</code> 및 그 하위만 감쌈</td>
</tr>
<tr>
<td><strong>상태 유지</strong></td>
<td>유지됨</td>
<td>안 됨 (컴포넌트 리셋됨)</td>
</tr>
</tbody></table>
<h3 id="5-errortsx-에러-컴포넌트">5. <code>error.tsx</code> (에러 컴포넌트)</h3>
<ul>
<li>페이지에서 발생한 에러를 처리하고 사용자에게 적절한 메시지를 표시</li>
<li>자동으로 에러를 캡처하여 UI로 렌더링 (Error Boundary)</li>
<li>사용자 친화적인 에러 처리 가능</li>
</ul>
<pre><code class="language-tsx">const Error = ({ error, reset }) =&gt; {
  return (
    &lt;div&gt;
      &lt;p&gt;에러가 발생했습니다: {error.message}&lt;/p&gt;
      &lt;button onClick={reset}&gt;다시 시도&lt;/button&gt;
    &lt;/div&gt;
  );
}

export default Error;</code></pre>
<h3 id="6-templatetsx-템플릿-컴포넌트">6. <code>template.tsx</code> (템플릿 컴포넌트)</h3>
<ul>
<li>페이지 전환 시 레이아웃 상태를 유지하지 않고 매번 초기화하여 렌더링</li>
<li>페이지 전환 애니메이션이나 새로 렌더링되는 UI에 적합</li>
<li>layout과 달리 상태 유지하지 않음<pre><code class="language-tsx">const Template = ({ children }) =&gt; {
return &lt;div className=&quot;transition-opacity animate-fadeIn&quot;&gt;{children}&lt;/div&gt;;
}
</code></pre>
</li>
</ul>
<p>export default Template;</p>
<pre><code>
## 4. 특수 라우팅
- 동적 라우팅 ([id])
  - URL 경로 일부를 **변수처럼 사용하는 방식**
  - 파일명에 대괄호를 사용하여 동적 파라미터를 받음.


**- URL 예시**
- `/posts/1`
- `/posts/hello-world`

**📘 코드 예시**
```tsx
// app/posts/[id]/page.tsx
import { useParams } from &#39;next/navigation&#39;;

const PostPage = () =&gt; {
  const params = useParams();
  return &lt;div&gt;Post ID: {params.id}&lt;/div&gt;;
}

export default PostPage;</code></pre><ul>
<li>병렬 라우팅 (@folder)<ul>
<li>여러 뷰(슬롯)를 동시에 렌더링하고 싶을 때 사용</li>
<li>@폴더명으로 병렬적으로 UI를 구성할 수 있음</li>
<li><code>layout.tsx</code> 안에서 <code>&lt;Slot /&gt;</code>으로 활용</li>
</ul>
</li>
</ul>
<pre><code class="language-graphql">app/
└── layout.tsx
└── @modal/
    └── page.tsx
└── @main/
    └── page.tsx</code></pre>
<pre><code class="language-tsx">// app/layout.tsx

const RootLayout = ({ modal, main}: {modal: React.ReactNode; main: React.ReactNode }) =&gt; {
  return (
    &lt;&gt;
      &lt;div&gt;{main}&lt;/div&gt;
      &lt;aside&gt;{modal}&lt;/aside&gt;
    &lt;/&gt;
  );
}

export default RootLayout;</code></pre>
<ul>
<li><p>인터셉트 라우팅 ((.) / (..) / (...))</p>
<ul>
<li>현재 페이지를 유지하면서 다른 경로로 이동한 것처럼 보이게 하는 라우팅</li>
<li>주로 모달, 사이드 패널 등을 라우팅으로 처리할 때 유용</li>
</ul>
</li>
<li><p>인터셉트 종류와 개념</p>
</li>
</ul>
<table>
<thead>
<tr>
<th>표현</th>
<th>의미</th>
<th>가로채는 대상 경로 예시</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>(.)</code></td>
<td>현재 경로 기준</td>
<td><code>/dashboard/(.)modal</code></td>
<td><code>dashboard</code> 하위에서만 모달 라우트를 가로챔</td>
</tr>
<tr>
<td><code>(..)</code></td>
<td>상위 1단계 경로까지</td>
<td><code>/dashboard/(..)/modal</code></td>
<td><code>dashboard</code> 외부 경로에서도 <code>modal</code>로 접근 가능</td>
</tr>
<tr>
<td><code>(...)</code></td>
<td>루트 기준, <strong>전체 앱 어디에서든</strong> 가로챔</td>
<td><code>/dashboard/(...)/modal</code></td>
<td>어느 경로에서든 <code>modal</code> 라우트를 인터셉트함</td>
</tr>
</tbody></table>
<p>예시 구조:</p>
<pre><code>app/
├─ dashboard/
│  ├─ page.tsx
│  └─ (...)/modal/
│     └─ page.tsx
├─ modal/         &lt;-- 일반 경로 (가로채지 않음)
│  └─ page.tsx
├─ layout.tsx</code></pre><ul>
<li><p>(.)modal/</p>
<ul>
<li>/dashboard/modal로 접근했을 때만 가로채짐</li>
<li>그 외 경로에서는 접근 불가 (에러)</li>
</ul>
</li>
<li><p>(..)/modal/</p>
<ul>
<li>/dashboard, /settings, 등 상위 1단계 경로에서도 modal을 가로챔</li>
<li>URL은 /dashboard/modal, /settings/modal 등으로 구성 가능</li>
</ul>
</li>
<li><p>(...)/modal/</p>
<ul>
<li>앱 전체 어디서든 modal 라우트를 가로챔</li>
<li>/any/route/modal 로 접근해도 가로채기 적용됨</li>
<li>가장 범용적인 방식</li>
</ul>
</li>
<li><p><strong>인터셉트 라우팅(intercepted routing)</strong>으로 설정한 디렉토리(ex - <code>modal</code>)는 Slot으로 동작.</p>
</li>
</ul>
<pre><code class="language-tsx">//app/dashboard/(...)/modal/page.tsx
&#39;use client&#39;

import { useParams, useRouter } from &#39;next/navigation&#39;;

const TestModal = () =&gt; {
  const params = useParams();
  const router = useRouter();

  const closeModal = () =&gt; {
    router.back(); // 뒤로 가기 (이전 경로로 복귀)
  };

  return (
    &lt;div
      style={{
        position: &#39;fixed&#39;,
        inset: 0,
        backgroundColor: &#39;rgba(0,0,0,0.6)&#39;,
        display: &#39;flex&#39;,
        justifyContent: &#39;center&#39;,
        alignItems: &#39;center&#39;,
        zIndex: 100,
      }}
    &gt;
      &lt;div
        style={{
          backgroundColor: &#39;#fff&#39;,
          padding: &#39;2rem&#39;,
          borderRadius: &#39;8px&#39;,
          width: &#39;400px&#39;,
          textAlign: &#39;center&#39;,
        }}
      &gt;
        &lt;h2&gt;모달 테스트&lt;/h2&gt;
        &lt;p&gt;Modal ID: {params.id}&lt;/p&gt;
        &lt;button className={&#39;bg-amber-400 rounded-2xl p-2 cursor-pointer&#39;} onClick={closeModal}&gt;닫기&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

export default TestModal;</code></pre>
<pre><code class="language-tsx">//app/layout.tsx - modal 추가
const RootLayout = async ({children, modal} :{children: React.ReactNode, modal: React.ReactNode}) =&gt; {

  const systemMode = await getSystemMode()
  const direction = &#39;ltr&#39;
  const session = await getServerSession(authOptions);

  return (
    &lt;html id=&#39;__next&#39; lang=&#39;en&#39; dir={direction} suppressHydrationWarning&gt;
      &lt;body className=&#39;flex is-full min-bs-full flex-auto flex-col&#39;&gt;
        &lt;InitColorSchemeScript attribute=&#39;data&#39; defaultMode={systemMode}/&gt;
        &lt;SessionProvider session={session}&gt;
          {children}
          {modal}
        &lt;/SessionProvider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  )
}

export default RootLayout</code></pre>
<h2 id="5-정리">5. 정리</h2>
<table>
<thead>
<tr>
<th>파일명</th>
<th>라우팅 역할</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td><code>page.tsx</code></td>
<td>페이지 컴포넌트 라우팅</td>
<td>페이지 UI 렌더링, entry-point</td>
</tr>
<tr>
<td><code>layout.tsx</code></td>
<td>공통 레이아웃 구성</td>
<td>상태 유지, Nested Layout 가능</td>
</tr>
<tr>
<td><code>route.ts</code></td>
<td>API 라우트 엔드포인트</td>
<td>HTTP 요청 처리</td>
</tr>
<tr>
<td><code>loading.tsx</code></td>
<td>로딩 상태 UI 표시</td>
<td>Suspense 활용한 비동기 처리 지원</td>
</tr>
<tr>
<td><code>error.tsx</code></td>
<td>에러 UI 표시 및 처리</td>
<td>에러 Boundary 역할</td>
</tr>
<tr>
<td><code>template.tsx</code></td>
<td>페이지 전환 시 상태 초기화</td>
<td>페이지 전환 효과(애니메이션 등)</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NextJS] 환경 변수 (.env)]]></title>
            <link>https://velog.io/@sh_yang/Next.js-%ED%99%98%EA%B2%BD-%EB%B3%80%EC%88%98-.env</link>
            <guid>https://velog.io/@sh_yang/Next.js-%ED%99%98%EA%B2%BD-%EB%B3%80%EC%88%98-.env</guid>
            <pubDate>Wed, 09 Apr 2025 08:18:17 GMT</pubDate>
            <description><![CDATA[<h1 id="환경-변수-env란">환경 변수 (.env)란?</h1>
<p>Next.js 프로젝트에서 환경 별 값을 설정하는 변수 값
<code>API_KEY</code>, <code>DB_URL</code>, <code>SECRET</code> 등 민감한 정보는 코드에 직접 작성하는 대신 환경변수를 통해 안전하게 관리</p>
<ul>
<li>구성
<img src="https://velog.velcdn.com/images/sh_yang/post/3e52521a-d086-4ab7-87e5-0ba1d2242ab0/image.png" alt=""></li>
</ul>
<hr>
<h2 id="1-환경변수-파일-종류">1. 환경변수 파일 종류</h2>
<p>Next.js는 환경에 따라 아래와 같은 파일을 자동 인식:</p>
<table>
<thead>
<tr>
<th>파일명</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>.env</code></td>
<td>기본 환경변수 (모든 환경에서 공통으로 사용)</td>
</tr>
<tr>
<td><code>.env.local</code></td>
<td><strong>로컬 개발용</strong> 환경변수 (Git에 커밋하지 않음)</td>
</tr>
<tr>
<td><code>.env.development</code></td>
<td><code>next dev</code> 실행 시 사용</td>
</tr>
<tr>
<td><code>.env.production</code></td>
<td><code>next build</code> &amp; <code>next start</code> 실행 시 사용</td>
</tr>
<tr>
<td><code>.env.test</code></td>
<td>테스트 환경에서 사용</td>
</tr>
</tbody></table>
<blockquote>
<p><strong><code>.env.local</code>은 Git에 커밋하면 안 됨.</strong> (<code>.gitignore</code>에 기본 포함됨)</p>
</blockquote>
<hr>
<h2 id="2-환경변수-선언-예시">2. 환경변수 선언 예시</h2>
<pre><code class="language-env"># .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
SECRET_KEY=my-secret-key</code></pre>
<h2 id="3-nextjs-환경변수-네이밍-규칙">3. Next.js 환경변수 네이밍 규칙</h2>
<p>Next.js에서는 환경변수를 선언할 때 <strong>접근 범위에 따라 변수명 규칙이 존재</strong>.</p>
<hr>
<ul>
<li>클라이언트에서 접근 가능한 변수: <code>NEXT_PUBLIC_</code> 접두사 필수</li>
<li>나머지는 서버 사이드에서만 접근 가능.</li>
</ul>
<table>
  <tr>
    <th colspan="2">변수명 / 접근 가능 위치</th>
  </tr>
  <tr style="border-top: 2px solid black;">
    <td><code>NEXT_PUBLIC_*</code></td>
    <td>✅ 클라이언트 & 서버 모두</td>
  </tr>
  <tr>
    <td>일반 변수 (<code>SECRET_KEY</code>, <code>DB_URL</code> 등)</td>
    <td>✅ 서버 전용 ❌ 클라이언트 불가</td>
  </tr>
</table>


<h2 id="4-환경변수-사용-방법-nextjs-기준">4. 환경변수 사용 방법 (Next.js 기준)</h2>
<p>Next.js에서는 <code>process.env.변수명</code>으로 환경변수를 사용할 수 있습니다.<br><strong>서버/클라이언트 위치에 따라 접근 방식이 다르며, <code>NEXT_PUBLIC_</code> 접두사가 중요</strong>.</p>
<hr>
<h3 id="4-1-서버-컴포넌트-또는-api-route에서-사용">4-1. 서버 컴포넌트 또는 API Route에서 사용</h3>
<p><code>.env</code> 또는 <code>.env.local</code>에 선언한 환경변수는 <strong>서버 코드에서 바로 사용</strong>가능.</p>
<pre><code class="language-env"># .env.local
SECRET_KEY=my-secret</code></pre>
<pre><code class="language-ts">// app/api/hello/route.ts
export async function GET() {
  const secret = process.env.SECRET_KEY
  return Response.json({ secret })
}</code></pre>
<ul>
<li><code>SECRET_KEY</code>는 클라이언트 코드에서는 접근할 수 없습니다. (보안 상 안전)</li>
</ul>
<h3 id="4-2-클라이언트-컴포넌트에서-사용-next_public_-붙이기">4-2. 클라이언트 컴포넌트에서 사용 (<code>NEXT_PUBLIC_</code> 붙이기)</h3>
<p>클라이언트 컴포넌트에서 환경변수를 사용하려면 반드시 <code>NEXT_PUBLIC_</code> 접두사가 필요.</p>
<pre><code># .env.local
NEXT_PUBLIC_API_URL=https://api.example.com</code></pre><pre><code class="language-tsx">// app/components/ClientComponent.tsx
&quot;use client&quot;

export default function ClientComponent() {
  const apiUrl = process.env.NEXT_PUBLIC_API_URL
  return &lt;p&gt;API 주소: {apiUrl}&lt;/p&gt;
}</code></pre>
<p><code>process.env.API_URL</code>처럼 접두사가 없는 변수는 브라우저에서는 undefined로 나옵니다.</p>
<h3 id="4-3-typescript에서-환경변수-타입-지정-선택">4-3. TypeScript에서 환경변수 타입 지정 (선택)</h3>
<ul>
<li>process.env는 기본적으로 모든 키를 string | undefined로 간주하므로 타입 오류가 날 수 있음.
이럴 땐 env.d.ts로 타입을 정의할 수 있음.</li>
</ul>
<pre><code class="language-ts">// env.d.ts
namespace NodeJS {
  interface ProcessEnv {
    NEXT_PUBLIC_API_URL: string
    SECRET_KEY: string
  }
}</code></pre>
<ul>
<li>env.d.ts 파일 정의 후, tsconfig.json 설정 확인</li>
</ul>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;strict&quot;: true,
    &quot;types&quot;: []
  },
  &quot;include&quot;: [
    &quot;next-env.d.ts&quot;,
    &quot;**/*.ts&quot;,
    &quot;**/*.tsx&quot;,
    &quot;env.d.ts&quot;,        // ✅ 여기에 직접 경로 추가
    &quot;types/env.d.ts&quot;   // ✅ types 폴더에 뒀다면 이렇게
  ]
}</code></pre>
<h3 id="4-4-환경변수-변경-시-dev-서버-재시작-필수">4-4. 환경변수 변경 시 dev 서버 재시작 필수</h3>
<p><code>.env</code> 파일을 수정한 경우에는 반드시 dev 서버를 재시작해야 반영 됨.</p>
<pre><code class="language-bash">npm run dev</code></pre>
<h2 id="5-클라이언트에-노출되면-안-되는-정보-예시">5. 클라이언트에 노출되면 안 되는 정보 예시</h2>
<ul>
<li>데이터베이스 주소 (DATABASE_URL)</li>
<li>시크릿 키 (SECRET_KEY)</li>
<li>외부 API 키 중 민감한 키 (예: Stripe Secret Key)</li>
</ul>
<p><strong>이런 값들은 절대 <code>NEXT_PUBLIC_</code>을 붙이지 않고, 서버에서만 사용해야 함.</strong></p>
<h2 id="6-정리">6. 정리</h2>
<ul>
<li><code>.env</code>는 프로젝트 루트에 위치</li>
<li>클라이언트에서 사용하려면 반드시 <code>NEXT_PUBLIC_</code> 접두사 붙이기</li>
<li>민감 정보는 <code>.env.local</code>에 작성 &amp; Git에 커밋하지 않기</li>
<li>환경에 따라 <code>.env.production</code>, <code>.env.development</code> 분리 가능</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[FastAPI] Database 연동 (SQLite / Postgresql)]]></title>
            <link>https://velog.io/@sh_yang/FastAPI-Database-%EC%97%B0%EB%8F%99-SQLite-Postgresql</link>
            <guid>https://velog.io/@sh_yang/FastAPI-Database-%EC%97%B0%EB%8F%99-SQLite-Postgresql</guid>
            <pubDate>Fri, 04 Apr 2025 23:31:33 GMT</pubDate>
            <description><![CDATA[<h2 id="1-sqlalchemy-db-드라이버-설치">1. SQLAlchemy, DB 드라이버 설치</h2>
<pre><code class="language-bash">pip install sqlalchemy psycopg2 asyncpg pydantic alembic</code></pre>
<table>
<thead>
<tr>
<th>라이브러리</th>
<th>하는 일</th>
<th>키워드</th>
</tr>
</thead>
<tbody><tr>
<td><strong>SQLAlchemy</strong></td>
<td>DB 테이블을 Python으로 다룸 (ORM)</td>
<td>테이블, 모델, 세션</td>
</tr>
<tr>
<td><strong>psycopg2</strong></td>
<td>PostgreSQL과 실제 연결</td>
<td>드라이버, PostgreSQL</td>
</tr>
<tr>
<td><strong>asyncpg</strong></td>
<td>PostgreSQL과 실제 연결 (비동기)</td>
<td>드라이버, PostgreSQL</td>
</tr>
<tr>
<td><strong>pydantic</strong></td>
<td>데이터 검증(Data Validation)과 직렬화(Serialization)를 자동으로 해주는 Python 라이브러리</td>
<td>Validation, 직렬화(Serialization)</td>
</tr>
<tr>
<td><strong>Alembic</strong></td>
<td>테이블 구조 변경을 추적하고 반영</td>
<td>마이그레이션, 자동화</td>
</tr>
</tbody></table>
<h2 id="2-database-설정">2. database 설정</h2>
<p><code>SAMPLE CODE</code></p>
<ul>
<li>동기 방식<pre><code class="language-python">database.py</code></pre>
</li>
</ul>
<hr>
<p>from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session</p>
<h1 id="sqlite">SQLite</h1>
<p>#=======================================================================================</p>
<h1 id="sqlalchemy-엔진-생성-동기-방식">SQLAlchemy 엔진 생성 (동기 방식)</h1>
<p>engine = create_engine(&quot;sqlite:///./{DatabaseName}.db&quot;, echo=True) #echo=True &gt; DB 작업 시 로그 출력
#=======================================================================================</p>
<h1 id="postgresql">PostgreSQL</h1>
<h1 id="접속-url">접속 URL</h1>
<p>DATABASE_URL = &quot;postgresql+psycopg2://{Username}:{Password}@{Host}:{Port}/{DatabaseName}&quot;</p>
<h1 id="sqlalchemy-엔진-생성">SQLAlchemy 엔진 생성</h1>
<p>engine = create_engine(
    DATABASE_URL, 
    echo=True  # SQL 로그 출력
)
#=======================================================================================</p>
<h1 id="sessionmaker는-세션을-생성해주는-팩토리-함수">sessionmaker는 세션을 생성해주는 팩토리 함수</h1>
<p>SessionLocal = sessionmaker(
    bind=engine,        # DB 엔진에 연결
    class_=Session,     # 사용할 세션 클래스 (기본값이므로 생략 가능)
    expire_on_commit=False  # 커밋 후 객체의 속성을 만료시키지 않음 (재조회 없이 사용 가능)
)</p>
<h1 id="모든-모델이-상속할-base-클래스-정의">모든 모델이 상속할 Base 클래스 정의</h1>
<p>Base = declarative_base()</p>
<h1 id="fastapi-의존성-주입용-함수">FastAPI 의존성 주입용 함수</h1>
<p>def get_db():
    db: Session = SessionLocal()  # 세션 인스턴스 생성
    try:
        yield db  # 세션을 외부에 제공
    finally:
        db.close()  # 요청 종료 시 세션 닫기</p>
<h1 id="테이블-자동-생성-함수-동기">테이블 자동 생성 함수 (동기)</h1>
<p>def create_tables():
    try:
        Base.metadata.create_all(bind=engine)
        print(&quot;테이블 생성 완료&quot;)
    except Exception as e:
        print(f&quot;테이블 생성 중 오류 발생: {e}&quot;)</p>
<pre><code>
- 비동기 방식

```python
database.py
------------------------------------------------
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker

# SQLite
#=======================================================================================
# SQLAlchemy 비동기 엔진 생성
engine = create_async_engine(&quot;sqlite+aiosqlite:///./{DatabaseName}.db&quot;, echo=True) #echo=True &gt; DB 작업 시 로그 출력
#=======================================================================================
# PostgreSQL
# PostgreSQL 접속 URL (asyncpg 드라이버 사용)
DATABASE_URL = &quot;postgresql+asyncpg://{Username}:{Password}@{Host}:{Port}/{DatabaseName}&quot;

# Async 엔진 생성
engine = create_async_engine(
    DATABASE_URL,
    echo=True  # SQL 로그 출력
)
#=======================================================================================

# 비동기 세션 팩토리 생성
AsyncSessionLocal = sessionmaker(
    bind=engine,               # 비동기 DB 엔진에 연결
    class_=AsyncSession,       # 사용할 세션 클래스 (비동기용)
    expire_on_commit=False     # 커밋 후에도 객체 속성 유지
)

# 모든 모델이 상속할 Base 클래스 정의
Base = declarative_base()

# FastAPI 의존성 주입용 비동기 함수
@asynccontextmanager
async def get_async_db():
    async with AsyncSessionLocal() as session:
        yield session  # 세션 제공 (자동으로 닫힘)

# 테이블 자동 생성
async def create_tables():
    try:
        async with engine.begin() as conn:
            await conn.run_sync(Base.metadata.create_all)
    except Exception as e:
        print(f&#39;Create Table 시도 중 오류 발생 : {e}&#39;)
</code></pre><table>
<thead>
<tr>
<th>항목</th>
<th>동기(Sync) 방식</th>
<th>비동기(Async) 방식</th>
</tr>
</thead>
<tbody><tr>
<td>사용 드라이버</td>
<td><code>psycopg2</code></td>
<td><code>asyncpg</code></td>
</tr>
<tr>
<td>엔진 생성</td>
<td><code>create_engine()</code></td>
<td><code>create_async_engine()</code></td>
</tr>
<tr>
<td>세션 클래스</td>
<td><code>Session</code></td>
<td><code>AsyncSession</code></td>
</tr>
<tr>
<td>세션 팩토리</td>
<td><code>SessionLocal = sessionmaker(...)</code></td>
<td><code>AsyncSessionLocal = sessionmaker(...)</code></td>
</tr>
<tr>
<td>커밋 설정</td>
<td><code>autocommit=False</code></td>
<td><code>expire_on_commit=False</code></td>
</tr>
<tr>
<td>주 용도</td>
<td>일반적인 Python / FastAPI 동기 API</td>
<td>FastAPI 비동기 API (<code>async def</code>) 환경에 적합</td>
</tr>
<tr>
<td>사용 예시</td>
<td><code>yield db</code></td>
<td><code>async with ...: yield session</code></td>
</tr>
<tr>
<td>장점</td>
<td>설정이 간단하고 익숙함</td>
<td>비동기 처리에 적합, 빠른 응답성</td>
</tr>
<tr>
<td>단점</td>
<td>동시성 처리에 한계</td>
<td>드라이버/모듈 호환성 신경 써야 함</td>
</tr>
</tbody></table>
<h2 id="3-데이터베이스-테이블-구조-정의">3. 데이터베이스 테이블 구조 정의</h2>
<p><code>models.py</code>는 데이터베이스 테이블과 매핑되는 ORM 모델 클래스들을 정의하는 파일.
SQLAlchemy를 이용하여 Python 클래스로 DB 테이블 구조를 선언하고, FastAPI에서 CRUD 등의 데이터 조작에 활용.</p>
<p><code>SAMPLE CODE</code></p>
<pre><code>models.py
------------------------------------------------
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Text
from sqlalchemy.orm import relationship
from database import Base
from datetime import datetime

# 사용자 테이블 정의
class User(Base):
    __tablename__ = &#39;users&#39;

    id = Column(Integer, primary_key=True, index=True)
    username = Column(String(50), unique=True, nullable=False)
    email = Column(String(100), unique=True, nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)

    # 관계 설정
    boards = relationship(&quot;Board&quot;, back_populates=&quot;owner&quot;)

# 게시판 테이블 정의
class Board(Base):
    __tablename__ = &#39;boards&#39;

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(200), nullable=False)
    content = Column(Text, nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)

    # 외래 키 (User 테이블과 연결)
    user_id = Column(Integer, ForeignKey(&quot;users.id&quot;))

    # 관계 설정
    owner = relationship(&quot;User&quot;, back_populates=&quot;boards&quot;)</code></pre><table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>Base</code></td>
<td>SQLAlchemy ORM의 모든 모델이 상속받는 베이스 클래스</td>
</tr>
<tr>
<td>클래스 이름</td>
<td>테이블 이름에 대응. 보통 대문자 카멜표기 사용 (<code>User</code>, <code>Board</code> 등)</td>
</tr>
<tr>
<td><code>__tablename__</code></td>
<td>DB 테이블 이름 (소문자 + 복수형으로 하는 경우가 많음)</td>
</tr>
<tr>
<td>컬럼 정의</td>
<td><code>Column()</code> 객체를 통해 필드 정의, 자료형, 제약조건 등 설정 가능</td>
</tr>
<tr>
<td>관계 설정</td>
<td><code>relationship()</code>, <code>ForeignKey()</code> 등을 사용해 테이블 간 관계 표현</td>
</tr>
</tbody></table>
<h2 id="4-데이터베이스-검증-및-직렬화">4. 데이터베이스 검증 및 직렬화</h2>
<p><code>schemas.py</code>는 FastAPI에서 <strong>데이터 검증 및 직렬화(Serialization)</strong> 를 위해 Pydantic 모델을 정의하는 파일.<br>클라이언트와 서버 간의 데이터 구조를 정의하고, API 요청 및 응답 데이터를 검증하는 역할을 함.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>BaseModel</code></td>
<td>모든 Pydantic 스키마가 상속받는 기본 클래스</td>
</tr>
<tr>
<td>필드 정의</td>
<td><code>str</code>, <code>int</code>, <code>datetime</code> 등의 타입을 사용해 필드 정의</td>
</tr>
<tr>
<td>유효성 검사</td>
<td><code>Field()</code>, <code>validator</code> 등을 사용해 입력 값 검증 가능</td>
</tr>
<tr>
<td>ORM 모드</td>
<td><code>Config</code> 내부에 <code>orm_mode = True</code>를 설정하여 SQLAlchemy 모델과 호환</td>
</tr>
<tr>
<td>요청/응답</td>
<td>FastAPI의 요청(Request) 및 응답(Response) 스키마로 활용</td>
</tr>
</tbody></table>
<p><code>SAMPLE CODE</code></p>
<pre><code class="language-python">schemas.py
------------------------------------------------
from pydantic import BaseModel, Field
from datetime import datetime
from typing import Optional

# 요청(Request) 스키마
class UserCreate(BaseModel):
    username: str = Field(..., min_length=3, max_length=50)
    email: str
    password: str = Field(..., min_length=6)

# 응답(Response) 스키마
class UserResponse(BaseModel):
    id: int
    username: str
    email: str
    created_at: datetime

    class Config:
        from_attributes = True  # ORM 모드 활성화 (SQLAlchemy 모델과 호환)

# 업데이트(Update) 스키마 (Optional 필드 포함)
class UserUpdate(BaseModel):
    username: Optional[str] = None
    email: Optional[str] = None</code></pre>
<h2 id="5-db와의-상호작용-로직">5. DB와의 상호작용 로직</h2>
<p><code>crud.py</code>는 FastAPI 프로젝트에서 DB와의 상호작용 로직을 모아놓은 파일로, 주로 다음 작업을 수행:</p>
<ul>
<li>데이터 생성 (Create)</li>
<li>데이터 조회 (Read)</li>
<li>데이터 수정 (Update)</li>
<li>데이터 삭제 (Delete)</li>
</ul>
<h3 id="특징">특징</h3>
<ul>
<li>서비스 로직과 분리되어 가독성과 유지보수 용이</li>
<li>비즈니스 로직의 중심이 아닌, DB 연산 담당</li>
<li>Pydantic 스키마와 SQLAlchemy 모델을 함께 사용</li>
</ul>
<pre><code class="language-python">crud.py
------------------------------------------------
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from models import User
from schemas import UserCreate, UserUpdate

# 사용자 생성
async def create_user(db: AsyncSession, user_create: UserCreate):
    new_user = User(**user_create.dict())
    db.add(new_user)
    await db.commit()
    await db.refresh(new_user)
    return new_user

# 사용자 조회 by ID
async def get_user(db: AsyncSession, user_id: int):
    result = await db.execute(select(User).where(User.id == user_id))
    return result.scalars().first()

# 전체 사용자 조회
async def get_users(db: AsyncSession, skip: int = 0, limit: int = 10):
    result = await db.execute(select(User).offset(skip).limit(limit))
    return result.scalars().all()

# 사용자 수정
async def update_user(db: AsyncSession, user_id: int, user_update: UserUpdate):
    result = await db.execute(select(User).where(User.id == user_id))
    user = result.scalars().first()
    if user:
        for field, value in user_update.dict(exclude_unset=True).items():
            setattr(user, field, value)
        await db.commit()
        await db.refresh(user)
    return user

# 사용자 삭제
async def delete_user(db: AsyncSession, user_id: int):
    result = await db.execute(select(User).where(User.id == user_id))
    user = result.scalars().first()
    if user:
        await db.delete(user)
        await db.commit()
    return user</code></pre>
<h2 id="6-라우터-연결-및-요청-연결">6. 라우터 연결 및 요청 연결</h2>
<pre><code class="language-python"># main.py
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session

import models, schemas, crud
from database import engine, get_db

asynccontextmanager로 lifespan 정의
@asynccontextmanager
async def lifespan(app: FastAPI):
    print(&quot;앱 시작 중... 테이블 생성 시도&quot;)
    #==============================================
    # 동기
    create_tables() # 시작 시 테이블 자동 생성
    #==============================================
    # 비동기
    await create_tables()  # 시작 시 테이블 자동 생성
    #==============================================
    yield
    print(&quot;앱 종료 중...&quot;)

app = FastAPI(lifespan=lifespan)

# POST: 사용자 생성
@app.post(&quot;/users/&quot;, response_model=schemas.UserResponse)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
    return crud.create_user(db=db, user=user)

# GET: 사용자 리스트 조회
@app.get(&quot;/users/&quot;, response_model=list[schemas.UserResponse])
def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
    users = crud.get_users(db, skip=skip, limit=limit)
    return users</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[FastAPI] IntelliJ 환경에서 FastAPI 시작하기]]></title>
            <link>https://velog.io/@sh_yang/FastAPI-IntelliJ-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-FastAPI-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sh_yang/FastAPI-IntelliJ-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-FastAPI-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 04 Apr 2025 06:31:15 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sh_yang/post/196823d2-66f0-4171-9643-7091db604810/image.png" alt="">
<img src="https://velog.velcdn.com/images/sh_yang/post/f1ec7a6b-3619-4214-b667-d951a7bb7818/image.png" alt=""></p>
<h2 id="1-python-플러그인-설치-한-번만-하면-됨">1. Python 플러그인 설치 (한 번만 하면 됨)</h2>
<ol>
<li>IntelliJ 실행</li>
<li>File &gt; Settings (Ctrl+Alt+S) &gt; Plugins</li>
<li>상단 Marketplace 탭에서 Python 검색</li>
<li>설치 후 IntelliJ 재시작</li>
</ol>
<h2 id="2-새-python-프로젝트-만들기">2. 새 Python 프로젝트 만들기</h2>
<ol>
<li><p>File &gt; New Project</p>
</li>
<li><p>왼쪽에서 Python 선택</p>
</li>
<li><p>아래 설정:</p>
<ul>
<li>Location: 프로젝트 경로</li>
<li>Python Interpreter:<ul>
<li>새 가상환경(venv) 만들거나</li>
<li>기존 Python 설치 선택</li>
</ul>
</li>
<li>Create 클릭</li>
</ul>
</li>
</ol>
<h2 id="3-fastapi--uvicorn-설치">3. FastAPI + Uvicorn 설치</h2>
<p><img src="https://velog.velcdn.com/images/sh_yang/post/d38be7f4-38c3-4438-a0de-d2d2ba6dbd6f/image.png" alt="">
▶ 터미널 열기</p>
<ul>
<li>IntelliJ 하단의 Terminal 탭 클릭</li>
<li>가상환경이 자동으로 활성화돼 있을 것임</li>
</ul>
<pre><code class="language-bash">pip install fastapi uvicorn</code></pre>
<h2 id="4-fastapi-서버-파일-생성">4. FastAPI 서버 파일 생성</h2>
<p>▶ main.py 파일 생성</p>
<pre><code class="language-python">from fastapi import FastAPI


app = FastAPI()

@app.get(&quot;/&quot;)
def read_root():
    return {&quot;message&quot;: &quot;Hello from IntelliJ + FastAPI!&quot;}</code></pre>
<h2 id="5-실행-설정-만들기-run-configuration">5. 실행 설정 만들기 (Run Configuration)</h2>
<p><img src="https://velog.velcdn.com/images/sh_yang/post/54848872-448e-42b3-b14d-eb4e58086d67/image.png" alt=""></p>
<ol>
<li>메뉴 상단 Run &gt; Edit Configurations</li>
<li>왼쪽 상단 + 버튼 → FastAPI 선택</li>
<li>이름: FastAPI</li>
<li>Script path: directories &gt; main.py </li>
<li>Uvicorn 옵션</li>
</ol>
<pre><code class="language-css">--reload</code></pre>
<ol start="6">
<li>Apply &gt; OK</li>
</ol>
<h2 id="6-실행">6. 실행</h2>
<ol>
<li>실행 버튼 클릭
<img src="https://velog.velcdn.com/images/sh_yang/post/dc6375f7-f5e8-469b-963f-b3203bc50dfe/image.png" alt="">
<img src="https://velog.velcdn.com/images/sh_yang/post/32e89eba-42da-4f13-abe6-d68dfb531c93/image.png" alt=""></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[FastAPI] 시작하기]]></title>
            <link>https://velog.io/@sh_yang/FastAPI-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sh_yang/FastAPI-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 04 Apr 2025 05:45:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sh_yang/post/6b815ceb-96a7-4466-8925-48f933e042aa/image.png" alt=""></p>
<h1 id="1-python--환경-준비">1. Python &amp; 환경 준비</h1>
<ul>
<li><p>Python 3.7 이상 설치 </p>
</li>
<li><p>패키지 설치 :: fastAPI / 실행환경(uvicorn)</p>
<pre><code>pip install fastapi uvicorn</code></pre></li>
</ul>
<h1 id="2-가상환경-만들기">2. 가상환경 만들기</h1>
<pre><code class="language-bash"># 프로젝트 폴더 생성
mkdir fastapi_project
cd fastapi_project

# 가상환경 생성
python -m venv venv

# 가상환경 활성화
venv\Scripts\activate</code></pre>
<h1 id="3-fastapi-기본-서버-파일-만들기">3. FastAPI 기본 서버 파일 만들기</h1>
<pre><code class="language-python">from fastapi import FastAPI

app = FastAPI()

@app.get(&quot;/&quot;)
def read_root():
    return {&quot;message&quot;: &quot;Hello, FastAPI!&quot;}</code></pre>
<h1 id="4-서버-실행">4. 서버 실행</h1>
<pre><code class="language-bash">uvicorn main:app --reload</code></pre>
<ul>
<li><p>--reload: 코드 수정 시 자동 재시작 (개발용)</p>
</li>
<li><p>브라우저에서 <a href="http://127.0.0.1:8000">http://127.0.0.1:8000</a> 접속</p>
</li>
<li><p>자동 생성된 문서:</p>
<blockquote>
<p>/docs (Swagger UI)
/redoc (ReDoc)</p>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NextJS] Dynamic Routes]]></title>
            <link>https://velog.io/@sh_yang/NextJS-Dynamic-Routes</link>
            <guid>https://velog.io/@sh_yang/NextJS-Dynamic-Routes</guid>
            <pubDate>Wed, 14 Feb 2024 15:05:59 GMT</pubDate>
            <description><![CDATA[<h1 id="dynamic-routes">Dynamic Routes</h1>
<ul>
<li>미리 정의된 URL항목으로 라우트하는 것이 아닌, 변경될 수 있는 Parameter 항목 등의 URL주소를 가질 수 있게 동적으로 처리하는 라우트 방식</li>
</ul>
<h3 id="expression">Expression</h3>
<ul>
<li>NextJS의 디렉토리 경로가 URL이 되는 것을 알 수 있습니다.</li>
<li>Dynamic Routes를 하기위해서는 마찬가지로 디렉토리를 이용하여 가능하며,
Group Route를 소괄호() 로 감싸서 처리한것과 비슷하게, 
대괄호[]를 이용하여 표현이 가능합니다.</li>
</ul>
<p><strong>Example)</strong></p>
<blockquote>
<p>app/test/[id] 디렉토리 생성 후, 아래와 같은 URL을 입력했을 때,
해당 page.tsx의 property로 다음과 같은 값을 받을 수 있습니다.</p>
<pre><code class="language-typescript">/test/1?value=testInfo&amp;value2=123</code></pre>
<hr>
<ul>
<li><strong>id</strong> : Dynamic Routes로 지정한 키값으로, params에서 값을 받을 수 있습니다.</li>
<li><strong>value, value2</strong> : 해당 URL의 parameter로 전달된 값으로, 
searchParams에서, object(key-value) 형식의 값으로 받아올 수 있습니다.
<img src="https://velog.velcdn.com/images/sh_yang/post/69fe1a87-1406-4ab0-a149-42cdcb342451/image.png" alt=""></li>
</ul>
</blockquote>
<h3 id="비슷한-예시">비슷한 예시</h3>
<ul>
<li><p>react-router-dom에서 사용하는 동적 라우트 경로와 유사하고,
router-dom에서 사용되는 아래의 동적 URL 할당과 유사합니다.</p>
<blockquote>
<p>/test/:id</p>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NextJS] Metadata]]></title>
            <link>https://velog.io/@sh_yang/NextJS-Metadata</link>
            <guid>https://velog.io/@sh_yang/NextJS-Metadata</guid>
            <pubDate>Mon, 05 Feb 2024 12:42:01 GMT</pubDate>
            <description><![CDATA[<h1 id="metadata">Metadata</h1>
<ul>
<li>기존 HTML 소스에서 &lt;meta&gt; 태그에 관한 설정을 가리키는 변수입니다.</li>
<li>Server Component에서만 동작이 가능합니다.</li>
</ul>
<h2 id="expression">Expression</h2>
<ul>
<li>layout 또는 page 파일에서만 사용이 가능합니다.</li>
</ul>
<blockquote>
<p>Javascript</p>
<pre><code class="language-javascript">export const metadata = {
    title: &quot;Head Title&quot;,
    description: &quot;Page Description&quot;,
}</code></pre>
<p>Typescript</p>
</blockquote>
<pre><code class="language-typescript">import { Metadata } from &#39;next&#39;;
export const metadata :Metadata = {
    title: &quot;Head Title&quot;,
    description: &quot;Page Description&quot;,
}</code></pre>
<ul>
<li>또한, title의 형식을 객체 형식으로 가져가는 것도 가능합니다.</li>
</ul>
<blockquote>
<pre><code class="language-typescript">export const metadata :Metadata = {
    title: {
       template: &quot;Home &gt; %s&quot;,
       default: &quot;Next Movies&quot;
    },
    description: &quot;Page Description&quot;,
}</code></pre>
</blockquote>
<ul>
<li>위의 template을 이용하여, 각 페이지별로 선언하는 title로 
%s의 리터럴을 대체할 수 있습니다.
<img src="https://velog.velcdn.com/images/sh_yang/post/27281755-f11f-4ee1-befe-77a98a6b8b20/image.PNG" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NextJS] Route Group]]></title>
            <link>https://velog.io/@sh_yang/NextJS-Route-Group</link>
            <guid>https://velog.io/@sh_yang/NextJS-Route-Group</guid>
            <pubDate>Mon, 05 Feb 2024 12:07:47 GMT</pubDate>
            <description><![CDATA[<h1 id="route-group">Route Group</h1>
<ul>
<li>NextJS에서 기본적으로 디렉토리가 있으면 일반적으로 URL에 매핑이 됩니다.</li>
<li>허나 Route Group의 기능을 사용하여 이를 URL 경로에 포함시키지 않을 수 있습니다.</li>
<li>또한, 라우트 그룹은 다음과 같은 경우에 유용합니다:</li>
</ul>
<p>1.사이트 섹션, 의도 또는 팀별로 경로를 구성 할 때
2.동일한 라우트 세그먼트 수준에서 중첩된 레이아웃을 활성화 할 때</p>
<ul>
<li>여러 루트 레이아웃을 포함하여 동일한 세그먼트에 여러 중첩 레이아웃 만들기</li>
<li>특정 세그먼트를 레이아웃으로 선택 할 때</li>
</ul>
<blockquote>
<p>기본적으로 Route Group을 사용하는 의미는, 코드 내부의 파일이 
어떠한 의도를 가졌는 지 설명할 수 있는 데에 의미를 두고있습니다.
예를 들면, Root 폴더 경로에서 Home 화면에 해당하는 page.tsx에 대해서 
Home이라는 의도를 부여하고 이를 넣는 등의 행위를 통해, 
<strong>&quot;이 page.tsx 파일은 Home화면에서 사용하는 파일이다.&quot;</strong>
와 같은 의도를 부여할 수 있습니다.</p>
</blockquote>
<h2 id="expression">Expression</h2>
<ul>
<li><p>Route Group의 표기방법은, 디렉토리 폴더명에 괄호를 묶음으로써 해결할 수 있습니다 =&gt; <strong>(:FolderName)</strong></p>
</li>
<li><p>Example) URL 경로에 영향을 주지 않고 라우트 구성
<img src="https://velog.velcdn.com/images/sh_yang/post/badb5bff-d734-453b-85e5-bb21889e725b/image.png" alt=""></p>
<ol>
<li>(home) 및 (movies)의 Route Group 하위로 폴더들이 생성이 되어있으나,
경로에 어떠한 영향도 미치지 않고, 위와 같이 라우팅이 적용됨을 알 수 있습니다.</li>
<li><strong>주의할 점 &gt; Route Group으로 묶인 경로 중, 동일한 경로를 만들어서는 안될 것입니다. 
예를 들면, (Home)/about-us 와 (Sub)/about-us는 둘 다 URL 경로상으로는
/about-us이기 때문에, 위와 같이 디렉토리를 구성해서는 오류가 날 것 입니다.</strong></li>
</ol>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NextJS] Rendering과 Hydration]]></title>
            <link>https://velog.io/@sh_yang/NextJS-Rendering%EA%B3%BC-Hydration</link>
            <guid>https://velog.io/@sh_yang/NextJS-Rendering%EA%B3%BC-Hydration</guid>
            <pubDate>Sat, 03 Feb 2024 09:47:41 GMT</pubDate>
            <description><![CDATA[<h1 id="1-rendering">1. Rendering</h1>
<blockquote>
<p><strong>1) Rendering이란?</strong></p>
<ul>
<li>React Component의 props, state에 따라서 뷰(HTML)을 형성하는 작업입니다.
한 마디로, React 코드로 작성된 Component를 HTML 소스로 변환하는 것을 뜻합니다.</li>
</ul>
<p><strong>2) Server Side Rendering / Client Side Rendering</strong></p>
<ol>
<li>기본적으로 NextJS는 React기반의 Framwork로써, Server Side Rendering의 방식을 사용합니다.</li>
<li>그 외에 create-react-app을 통해 직접 생성되는 React Application은 Client Side Rendering의 방식을 사용합니다.</li>
</ol>
<ul>
<li>즉, React 구조에서는 두 가지 방식이 존재하며, 두 가지 방식의 단어들의 뜻을 직역하면, 각각 다음과 같습니다.
SSR(Server Side Rendering) : Server에서 Rendering이 이루어지는 것을 의미합니다.
CSR(Client Side Rendering) : Client에서 Rendering이 이루어지는 것을 의미합니다.</li>
</ul>
<p><strong>3) CSR과 SSR의 특징 및 차이점</strong></p>
<ul>
<li>React에서 제공이 가능한 두 가지 방식의 Render는 그 특징만큼이나, 차이점 역시 큽니다.</li>
</ul>
</blockquote>
<style>
.slash {
  background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><line x1="0" y1="100%" x2="100%" y2="0" stroke="gray" /></svg>');
}
.backslash {
  background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><line x1="0" y1="0" x2="100%" y2="100%" stroke="gray" /></svg>');
}
table {
    border-collapse: collapse;
    border-top: 1px solid gray;
    border-left: 1px solid gray;
}  
table.table-fixed {
      table-layout: fixed;
}
th, td {
    border-bottom: 1px solid gray;
    border-right: 1px solid gray;
    padding: 5px;
}
</style>
<table class="table table-fixed">
  <thead>
    <tr>
      <th class="backslash" style="width: 20%;">
        <div style="text-align: right; font-size: 14px;">
          Rendering
        </div>
        <div style="text-align: left; font-size: 14px">
          Remarks
        </div>
      </th>
      <th style="width: 40%; font-size:21px;">CSR (Client Side Rendering)</th>
      <th style="width: 40%; font-size:21px;">SSR (Server Side Rendering)</th>
      </tr>
  </thead>
  <tbody>
    <tr style="height:3.5rem;">
      <td style="text-align:center;">
        <span style="font-weight:bold; font-size: 24px;">특징</span>
      </td>
      <td style="padding: 1rem;">
        <div>
          1. Rendering이 Client(웹 브라우저)에서 발생한다. <br/>
          2. 서버에서 별도로 처리해주는 스크립트는 없다. <br/>
          3. 클라이언트에서 JS를 다운로드 받는 동안 활성화되는 HTML 소스는 없다.
        </div>
      </td>
      <td style="padding: 1rem;">
        <div>
          1. Rendering이 Server에서 발생한다. <br/>
          2. 서버에서 초기 화면에 대한 렌더링을 별도로 처리해준다. <br/>
          3. 클라이언트에서 JS를 다운로드 받는 동안 미리 렌더링한 html소스를 보여준다.
        </div>
      </td>
      </tr>
    <tr style="height:10rem;">
      <td style="text-align:center;">
        <span style="font-weight:bold; font-size: 24px;">장점</span>
      </td>
      <td style="text-align:left; padding:1rem;">
        <div>
          1. 웹 페이지 Loading시, 서버에 부하가 적다 <br/>
          2. SPA(Single Page Application)방식으로 불러오기 때문에, <br/>
             &nbsp;&nbsp;&nbsp;한번 불러온 다음 페이지 전환 시 빠르다. <br/>
        </div>
      </td>
      <td style="text-align:left; padding:1rem;">
        <div>
          1. MPA(Multi Page Application)방식으로 불러오기 때문에,<br/>
             &nbsp;&nbsp;&nbsp;페이지 최초 로딩 시 화면이 빠릅니다.<br/>
          2. 인터넷이 느린 상황에서도 최초에 화면을 렌더링 된 상태로<br/>
             &nbsp;&nbsp;&nbsp;받기 때문에, 내용이 즉각 나타난다<br/>
          3. 검색엔진 최적화(SEO)에 좋다.
        </div>
      </td>
      </tr>
    <tr style="height:5rem;">
      <td style="text-align:center;">
        <span style="font-weight:bold; font-size: 24px;">단점</span>
      </td>
      <td>
        <div>
          1. 최초 페이지 로딩 시, 전체 페이지에 JS를 한번에 받기 때문에,<br/>
          &nbsp;&nbsp;&nbsp;인터넷이 느린 환경에서는 로딩이 오래 걸릴 수 있다.<br/>
          2. 검색 엔진 최적화(SEO)에 취약하여 잘 드러나지 않는다.<br/>
          3. 일반 HTML 렌더링에 대한 처리도 js를 통해 하기때문에, <br/>
          &nbsp;&nbsp;&nbsp;자바스크립트를 비활성화 시, 화면이 나타나지 않는다.<br/>
        </div>
      </td>
      <td>
        <div>
          1. 페이지 전환 시, SPA와 다르게 최초의 로딩을 반복한다.<br/>
          2. 서버에서 최초 렌더링 작업을 수행 후 클라이언트로 넘기기<br/>
             &nbsp;&nbsp;&nbsp;때문에, CSR 대비 서버에 부하가 있는 편이다.
        </div>
      </td>
      </tr>
  </tbody>
</table>


<h1 id="2-hydration">2. Hydration</h1>
<blockquote>
<ul>
<li><p><strong>React의 Hydration</strong>
Server Side Rendering을 통해서, 서버에서 렌더링된 HTML소스를 받은 다음, 기존 CSR에서 사용하는 방식과 
같이 Javascript로 된 React Component로 Load하는 작업을 수행하는 것을 Hydration이라고 합니다.</p>
</li>
<li><p>NextJS에서 사용하는 Anchor 태그 (<strong>&lt;a&gt;</strong>)는 최초에 서버에서 렌더링된 HTML로 호출이 된 다음, 
React App으로 Hydrate 작업을 거쳐서 <strong>&lt;LinkComponent&gt;</strong>로 변경 됩니다.</p>
</li>
</ul>
<hr>
<p><strong><em>Hidration Diagram</em></strong></p>
<p><img src="https://velog.velcdn.com/images/sh_yang/post/2b9a09d2-c7e7-4556-a46e-2021ca5272ab/image.png" alt="">
Reference. <a href="https://aboutmonica.com/blog/server-side-rendering-react-hydration-best-practices">Keeping Server-Side Rendering Cool With React Hydration (Link)</a></p>
</blockquote>
<h1 id="3-use-client">3. &quot;use client&quot;</h1>
<ul>
<li><p>SSR 방식으로 운용되는 nextJS에서는 초기에 HTML을 반환하고, 
React Component로 변환하는 Hydration 작업을 수행합니다.
이 때, Hydration을 하게되는 컴포넌트에는 
아래와 같이 키워드를 1번째 줄에 입력해야 합니다.</p>
<blockquote>
<p><strong>&quot;use client&quot;</strong></p>
<hr>
<ul>
<li>hook을 사용 중이지만, &quot;use client&quot; 커맨드를 추가하지 않았을 경우, 아래 사진과 같이 오류가 나타납니다.
<img src="https://velog.velcdn.com/images/sh_yang/post/9c083f38-9d4e-4183-90d3-eb5a61e24121/image.png" alt=""></li>
</ul>
<hr>
<ul>
<li><p>위와 같이 hook 함수를 사용하기 위해서는 Client Component이어야 하며, use client는 가장 최상단 줄에 입력을 합니다.</p>
</li>
<li><p>한번 use client를 사용한 Component인 경우, 이 안에서 렌더링 된 Component들은 전부 Client Component로 인식됩니다.</p>
</li>
</ul>
<p><strong>&gt; Navigation.tsx / &gt; Test.tsx</strong>
<img src="https://velog.velcdn.com/images/sh_yang/post/f3600b90-eb38-4abb-aafb-5373e5ce1ba8/image.png" alt=""></p>
<ul>
<li><strong>&quot;use client&quot;</strong>에 대한 선언은 항상 최상위 줄에 명시 되어야 합니다.</li>
<li>Navigation 컴포넌트 안에 렌더링 된 Test 컴포넌트는 Client Component에서 사용하고 있기 때문에,</li>
<li>*&quot;use client&quot;** 선언 없이도 hook 함수를 사용할 수 있습니다.</li>
</ul>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NextJS]Routes
]]></title>
            <link>https://velog.io/@sh_yang/NextJSRoutes</link>
            <guid>https://velog.io/@sh_yang/NextJSRoutes</guid>
            <pubDate>Fri, 02 Feb 2024 14:31:15 GMT</pubDate>
            <description><![CDATA[<h1 id="시작하기-앞서">시작하기 앞서...</h1>
<blockquote>
<p><strong>NextJS 프레임워크 Workspace가 있어야 합니다.</strong> 
<a href="https://velog.io/@sh_yang/NextJS-Project-Workspace-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0">이전 게시글 참조 ----&gt; [NextJS]Project Workspace 시작하기</a></p>
</blockquote>
<h2 id="nextjs-defining-routes">NextJS Defining Routes</h2>
<ul>
<li><p>기본적으로, NextJS의 기본적인 구성은 다음과 같습니다.</p>
<blockquote>
<p><strong>app</strong>
<strong>├layout.tsx</strong>
<strong>├page.tsx</strong>
<strong>├node_modules</strong>
<strong>├next-env.d.ts</strong>
<strong>├package-lock.json</strong>
<strong>├package.json</strong>
<strong>└tsconfig.json</strong></p>
</blockquote>
</li>
<li><p>여기서, pages.tsx 파일의 역할은 index.html 파일의 역할과 동일하며,</p>
</li>
<li><p><em>NextJS*</em>에서의 Route 설정은 app 폴더의 디렉토리 경로를 통해서 결정됩니다.</p>
</li>
</ul>
<blockquote>
<p><strong><em>Example)</em></strong></p>
<p><strong>localhost:3000/dev</strong> ==&gt;  /app/dev/page.tsx
<strong>localhost:3000/dev/uat</strong> ==&gt;  /app/dev/uat/page.tsx
<strong>localhost:3000/prod</strong> ==&gt;  /app/prod/page.tsx</p>
<p>이 때, page.tsx에서는 아래의 내용과 같이, 컴포넌트에 대해서 <strong><em>export default</em></strong> 커맨드가 걸려있어야 합니다.</p>
<blockquote>
<p><strong>&gt; app/dev/page.tsx</strong></p>
<pre><code class="language-javascript">export default function Dev() {
    return &lt;h1&gt;Dev Page&lt;/h1&gt;
}</code></pre>
</blockquote>
<p>또한, 폴더 경로 안에 <strong>page.tsx</strong> 파일이 없는 경우, 
해당 경로에 접속하게 되는 경우 404 Not found 오류를 반환합니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NextJS]Project Workspace 시작하기]]></title>
            <link>https://velog.io/@sh_yang/NextJS-Project-Workspace-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sh_yang/NextJS-Project-Workspace-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 02 Feb 2024 14:08:54 GMT</pubDate>
            <description><![CDATA[<h1 id="setting-steps">Setting Steps</h1>
<h2 id="시작하기-앞서">시작하기 앞서...</h2>
<blockquote>
<p><strong>node.js 20.11.0 LTS 버전을 설치합니다</strong>  <a href="https://nodejs.org/en">(NODE.JS 다운로드 바로가기)</a></p>
</blockquote>
<h2 id="project-workspace-구성">Project Workspace 구성</h2>
<ol>
<li>처음 NextJS 프로젝트의 디렉토리가 될 Workspace 공간을 생성합니다.
Ex) C:\Documents\nextjs-workspace\</li>
</ol>
<ol start="2">
<li><p>아래 커맨드를 입력하여 package.json을 생성합니다.</p>
<blockquote>
<p><strong>npm init -y</strong>
그러면, 아래와 같은 결과가 출력이 됩니다.</p>
<p><strong>&gt; package.json</strong></p>
<blockquote>
<pre><code class="language-json">{
&quot;name&quot;: &quot;learn-nextjs14&quot;,
&quot;version&quot;: &quot;1.0.0&quot;,
&quot;description&quot;: &quot;&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;keywords&quot;: [],
&quot;author&quot;: &quot;&quot;,
&quot;license&quot;: &quot;ISC&quot;
}</code></pre>
</blockquote>
</blockquote>
</li>
<li><p>package.json 항목의 내용을 아래와 같이 수정합니다.</p>
</li>
</ol>
<ul>
<li>license : &quot;MIT&quot;</li>
<li>scripts : { &quot;dev&quot;: &quot;next dev&quot; }</li>
</ul>
<ol start="4">
<li><p>아래 커맨드를 입력하여 next-js의 구성 파일을 다운로드 받는다.</p>
<blockquote>
<p><strong>npm install react@latest react-dom@latest next@latest</strong></p>
<blockquote>
<p>해당 시점에서, next@latest의 버전은 14.1.0버전이며, 
<strong>Node.js 18.17.0버전 이상</strong>부터 다운로드 받을 수 있다.</p>
<p>Node.js의 버전이 낮은 경우, 아래와 같이 오류 문구가 발생합니다.
<img src="https://velog.velcdn.com/images/sh_yang/post/fb445baf-d539-4040-a9a2-dbb8599f6483/image.png" alt="">
구성파일을 다운로드 받게되면, node-modules 등의 폴더가 생성된다.</p>
</blockquote>
</blockquote>
</li>
<li><p>app/page.tsx 또는 app/page.jsx 파일 생성</p>
<blockquote>
<p>NextJS에서는 app 디렉토리의 page.jsx 또는 page.tsx 파일이 index.js의 역할을 수행한다.
파일 내용은 아래와 같이 입력합니다.</p>
<blockquote>
<p><strong>&gt; page.tsx</strong></p>
<pre><code class="language-javascript">export default function Page() {
 return &lt;h1&gt;Hello NextJS&lt;/h1&gt;
}</code></pre>
</blockquote>
</blockquote>
</li>
<li><p>커맨드를 다음과 같이 입력합니다.</p>
<blockquote>
<p><strong>npm run dev</strong> </p>
<p>그러면, 다음과 같은 화면과 함께 실행 화면이 나타납니다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/sh_yang/post/eb61d267-e2f1-4d48-ae8b-77cc6e1c7fcd/image.png" alt=""></p>
<ul>
<li>위의 사진의 문구를 보면, 아래의 Log내용이 있음을 확인할 수 있습니다.
[<strong>Your page app/page.tsx did not have a root layout. We created app/layout.tsx for you.</strong>]</li>
<li>위의 내용을 토대로, page.tsx를 렌더링 하기 위해서는 nextJS에서는 layout.tsx가 필요하며, 
파일이 없는 경우 직접 생성해 주는 것을 확인할 수 있습니다.</li>
</ul>
<p><strong>&gt; layout.tsx</strong></p>
<pre><code class="language-javascript">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>
</blockquote>
</blockquote>
</li>
<li><p>위의 사진과 같이, localhost:3000  경로로 웹페이지가 나타나게 되며 아래의 화면과 같이 나타납니다.
<img src="https://velog.velcdn.com/images/sh_yang/post/822b3dcb-8697-465f-8489-a0643c71d64b/image.png" alt=""></p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[MS-SQL]Database 백업 파일 복원]]></title>
            <link>https://velog.io/@sh_yang/MS-SQLDatabase-%EB%B0%B1%EC%97%85-%ED%8C%8C%EC%9D%BC-%EB%B3%B5%EC%9B%90</link>
            <guid>https://velog.io/@sh_yang/MS-SQLDatabase-%EB%B0%B1%EC%97%85-%ED%8C%8C%EC%9D%BC-%EB%B3%B5%EC%9B%90</guid>
            <pubDate>Mon, 15 Jan 2024 02:28:10 GMT</pubDate>
            <description><![CDATA[<h1 id="데이터베이스-백업-파일-복원">데이터베이스 백업 파일 복원</h1>
<h3 id="백업-복원하기에-앞서">백업, 복원하기에 앞서...</h3>
<ol>
<li>SSMS 프로그램을 설치해야 합니다. <a href="https://learn.microsoft.com/ko-kr/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver16">[SSMS 다운로드 바로가기]</a></li>
<li>되도록이면 MS-SQL 최신버전을 설치해야 합니다.</li>
<li>SQL Server가 설치되어 있어야 합니다.</li>
</ol>
<ul>
<li>저의 경우, Docker를 이용하여 Container에 2022-latest버전을 설치하였습니다.</li>
</ul>
<h2 id="데이터베이스-백업">데이터베이스 백업</h2>
<p><img src="https://velog.velcdn.com/images/sh_yang/post/a9bae933-4444-4fa1-8993-7f07646a9ab2/image.png" alt="데이터베이스 백업"></p>
<ol>
<li>SSMS 모듈에서 백업을 진행할 데이터베이스를 우클릭합니다.</li>
<li>우클릭 후 나타나는 CONTEXT화면은 위와 같이 나타나며, [태스크 &gt;백업]을 누릅니다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sh_yang/post/90c43cb3-0866-468c-aba9-5482ced97dd7/image.png" alt=""></p>
<ol>
<li>데이터베이스 백업 창이 위와같이 나타나면 백업위치가 디스크인지 확인합니다.</li>
<li>디스크로 설정 후, 백업 위치가 없는 경우 추가하여 설정하며, 이미 있으면 확인을 누릅니다.</li>
<li>확인 버튼 클릭 후, 백업 파일을 확인합니다.</li>
</ol>
<h2 id="데이터베이스-복원">데이터베이스 복원</h2>
<p><img src="https://velog.velcdn.com/images/sh_yang/post/4df09edd-91fb-4ee6-9795-6ae446e16748/image.png" alt=""></p>
<ol>
<li>복원을 진행할 데이터베이스를 우클릭합니다.</li>
<li>[태스크 &gt; 복원 &gt; 데이터베이스]를 순차적으로 클릭합니다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sh_yang/post/1967ab77-c07f-490a-b806-81fe98b56022/image.png" alt=""></p>
<ol>
<li>데이터베이스 복원 화면에서, 원본의 설정을 [디바이스]로 변경합니다.</li>
<li>디바이스 옆의 [...] 버튼을 클릭합니다.</li>
<li>그러면 백업 디바이스 선택 창이 나타나는데, 여기서 <strong>[백업 미디어 유형]</strong>은 <strong>[파일]</strong>로 합니다.</li>
<li><strong>[추가]</strong> 버튼을 클릭하여, 백업할 파일을 선택합니다. (.bak가 아닌 다른 확장자를 했을 경우, 모든 파일 탭으로 설정하여 파일을 선택합니다.</li>
</ol>
<h3 id="데이터베이스-복원-시-오류-케이스">데이터베이스 복원 시, 오류 케이스</h3>
<h4 id="1-데이터베이스-버전-설정-문제">1. 데이터베이스 버전 설정 문제</h4>
<blockquote>
<p>System.Data.SqlClient.SqlError: The database was backed up on a server running version <strong>${BACKUP_VERSION}</strong>. That version is incompatible with this server, which is running version <strong>${RECOVERY_VERSION}</strong>. Either restore the database on a server that supports the backup, or use a backup that is compatible with this server. (Microsoft.SqlServer.SmoExtended)</p>
</blockquote>
<ul>
<li>백업파일의 버전이 복원하려는 버전보다 높은 경우 발생하는 내용으로, 복원하려는 서버의 버전을 업그레이드 해야 합니다.</li>
<li>만약, 업그레이드 할 수 없는 경우, [DB 데이터 내보내기]를 이용한 방법으로 데이터를 받아야합니다.</li>
</ul>
<h4 id="2-데이터베이스-복원-파일-덮어쓰기-미설정">2. 데이터베이스 복원 파일 덮어쓰기 미설정</h4>
<blockquote>
<p>System.Data.SqlClient.SqlError: The backup set holds a backup of a database other than the existing ‘${database}’ database</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sh_yang/post/4fe6e73d-6548-4a2c-88f6-a4d9323c7fde/image.png" alt=""></p>
<ul>
<li>위와 같이 파일의 덮어쓰기 설정만 활성화하면 됩니다. (WITH REPLACE)</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>