Pre-rendering과 Data Fetching

  • Pre-rendering: Next.js는 기본적으로 모든 페이지를 미리 렌더링해둠 → JavaScript를 허용하지 않아도 접속할 수 있음

  • Static Generation: 빌드 시에 HTML을 미리 렌더링해두고, 요청할 때마다 재사용하는 방식, 데이터 업데이트가 적은 페이지에 권장됨

    • 외부에서 데이터 요청을 해야하는 경우, getStaticProps 라는 비동기 함수 사용
    • 페이지에서만 export 가능
    • 이 함수는 개발 시에는 매 요청마다 동작하지만, production 시에는 빌드할 때만 서버에서만 동작함 → DB 쿼리도 직접 작성할 수 있음
    ... 
    export async function getStaticProps() { 
        const res = await fetch('..'); 
        return res.json(); 
    }
    
    export default function Home({ allPostsData }) { 
    ...
  • Server-side Rendering: 요청할 때마다 서버에서 HTML을 렌더링하는 방식

    • 외부에서 데이터 요청을 해야하는 경우, getServerSideProps 라는 비동기 함수 사용
    export async function getServerSideProps(context) { 
        return { 
            props: { 
                // props for your component 
            }, 
        }; 
    }
    • 하나의 프로젝트 내에서 두 방식을 혼용할 수 있음

API Routes

  • pages 폴더에 api 폴더 생성

  • 서버에서만 동작함

  • getStaticProps 혹은 getStaticPaths 에서 API 루트를 가져오면 안 됨

  • form 입력 처리 시 API를 사용하면 바로 DB에 저장할 수 있음

    // pages/api/hello.js 
    
    export default function handler(req, res) { 
    res.status(200).json({ text: 'Hello' }); 
    }

※ 참고 링크
https://nextjs.org/learn/basics/create-nextjs-app/setup

'Development > Next.js' 카테고리의 다른 글

02. Next.js 프로젝트 생성, 페이지 이동  (0) 2023.02.12
01. Next.js 개요  (0) 2023.02.05

프로젝트 생성

create-next-app

npx create-next-app 프로젝트명

// 예제
npx create-next-app nextjs-blog --example "https://github.com/vercel/next-learn/tree/master/basics/learn-starter"
  • TypeScript, eslint 사용 여부는 팀에서 결정

폴더 구조

  • pages: React 컴포넌트를 작성하는 폴더, 파일 경로가 곧 url 주소임
    • ex. pages/index.js = /
      pages/posts/first-post.js = /posts/first-post
  • public: 정적 파일을 저장하는 폴더
  • styles: css 파일을 작성하는 폴더

페이지 이동

Link 컴포넌트

  • a 태그와 동일하게 href 속성에 이동할 url 지정
import Link from "next/link";

export default function FirstPost() {
  return (
    <>
      <h1>First Post</h1>
      <h2>
        <Link href="/">Back to home</Link>
      </h2>
    </>
  );
}

Dynamic URL

  • 페이지 파일명을 대괄호 []로 감싸야 함

  • 모든 경로를 포함하려면 ... 을 추가(ex. […id].js)

  • getStaticPaths 라는 비동기 함수 사용

    • 유효한 id 목록을 반환함
    • fallback이 false일 경우, 반환되는 paths에 없는 경로라면 404 페이지를 보여줌
    • fallback이 true일 경우, 404 페이지가 아닌 fallback 버전이 보여짐
    • fallback이 ‘blocking’일 경우, 새로운 경로가 getStaticProps 로 서버에서 렌더링되고 이후 요청을 위해 캐싱됨
  • 이 함수는 개발 시에는 매 요청마다 동작하지만, production 시에는 빌드할 때만 서버에서만 동작함

  • 예제: 상세 게시글 조회

    // pages/posts/[id].js
    // 경로의 id가 유효하다면 params.id를 사용해 데이터를 가져와 상세 게시글 조회
    
    import Layout from '../../components/layout';
    import { getAllPostIds } from '../../lib/posts';
    
    // 렌더링할 내용
    export default function Post() {
      return <Layout>...</Layout>;
    }
    
    // 유효한 id 목록 생성 후, 요청한 id가 유효한지 판단
    export async function getStaticPaths() {
      const paths = getAllPostIds();
      return {
        paths,
        fallback: false,
      };
    }
    
    // params.id를 사용해 데이터 가져오는 작업 수행
    export async function getStaticProps({ params }) {
      // To Do
    }
    // lib/posts.js 
    // 유효한 게시글의 id 목록을 반환하는 함수 getAllPostIds 정의 
    
    import fs from "fs"; 
    import path from "path"; 
    
    const postsDirectory = path.join(process.cwd(), "pages/posts"); 
    
    export function getAllPostIds() { 
        const fileNames = fs.readdirSync(postsDirectory); 
        return fileNames.map((fileName) => { 
            return { // params라는 키를 가진 객체여야 함 
                params: { // 키 이름은 파일 이름([] 안에 있는)과 동일해야 함 
                    id: fileName.replace(/\.md$/, ""), 
                }, 
            }; 
        }); 
    }

기타

  • 라우터는 next/router의 useRouter 훅으로 접근할 수 있음

  • 404 페이지를 커스텀하려면 pages 폴더에 404.js 파일 생성

    // pages/404.js 
    
    export default function Custom404() { 
        return <h1>404 - Page Not Found</h1>; 
    }

※ 참고 링크
https://nextjs.org/learn/basics/create-nextjs-app/setup

'Development > Next.js' 카테고리의 다른 글

03. Next.js Pre-rendering, Data Fetching, API Routes  (0) 2023.02.18
01. Next.js 개요  (0) 2023.02.05

Django.zip
18.00MB

 

1. 압축 해제 후 manage.py가 있는 폴더에서 터미널 실행 

2. `source venv/Scripts/activate`로 가상환경 실행

3. `python manage.py runserver`로 서버 실행

4. 브라우저에서 `http://127.0.0.1:8000/myapp/`로 접속

 

※ 강의 링크: https://inf.run/DmuA

Next.js란?

React 라이브러리를 기반으로 한 프레임워크로, CSS, styled-jsx, SCSS를 지원함

주요 기능

  • SSR(Server Side Rendering) + CSR과 혼합 사용 가능
    • SSR: 서버에서 데이터까지 포함된 페이지를 렌더링한 뒤 브라우저에 전달하는 방식
      • 장점: 검색엔진 최적화(SEO) 가능, 첫 페이지 로딩 시간이 짧음
      • 단점: 페이지 이동 시 화면이 깜빡거리고, 서버에서 렌더링하기 때문에 서버에 부하 발생
  • 정적 사이트 생성(SSG) 가능
  • Automatic Routing : pages 폴더에 있는 파일 이름으로 라우팅, 직관적
  • Dynamic URL : [ ]문법으로 가변적으로 변하는 url에 동적 url을 만들 수 있음
  • Single File Components : 해당 컴포넌트에서만 스코프를 가지는 css를 만들 수 있음
  • Client-Side Navigation : Link 컴포넌트를 사용하여 해당 컴포넌트가 뷰포트에 보이면 관련 페이지를 백그라운드에 미리 가져와 페이지간 빠르고 매끄러운 이동 가능
  • Code Splitting: dynamic import를 사용하여 모듈이 호출될 때에만 모듈을 import → 로딩 시간을 줄여줌

13 버전의 특징

app/ Directory(beta 버전)

  • 파일 시스템 기반 라우팅 → 디렉터리 기반 라우팅
  • /app/page.js: 고유한 ui 정의
  • /app/layout.js: 여러 경로에서 공유하는 ui 정의(nave, footer 등)

Data Fetching

  • 렌더링 과정에서 보여지는 깜빡임을 방지하기 위해 pre-rendering을 위한 data fetching 지원
  • 기존에는 해당 기능을 getStaticProps와 getServerSideProps로 사용했으나, 13 버전부터는 use를 사용해야 함

기타 업그레이드 사항

  • Image 컴포넌트의 자동 최적화: 로컬 이미지의 경우 로딩 중 자동으로 width와 height 설정 → 로딩 중 기존 레이아웃의 밀림 방지, lazy loading 기법으로 로딩 시간 단축
  • 폰트도 자동으로 layout shift 방지
  • 구글 폰트 내장
  • Link 컴포넌트 내에 a 태그 사용하지 않아도 됨, props나 함수 사용 가능

※ 참고 링크
https://velog.io/@syoung125/Next.js-기본-개념-1-Next.js-란-Next.js를-왜-사용할까-Next.js의-장점은
https://velog.io/@jo_love/TIL100.-Next.JS-13-새로운-특징들

GET과 POST

HTTP Method, 즉 HTTP 프로토콜을 통해서 서버에 값을 전달할 때 사용하는 방식의 종류, 이 외에 다른 방식들도 있음
하나의 URL에 요청을 보낼 때 GET과 POST 방식을 모두 사용할 수 있음, 방식을 기준으로 분기를 나누어 처리하면 됨

GET

  • URL에 파라미터 값으로 데이터를 전달 → URL에 파라미터가 보여짐, 비교적 보안이 취약함
  • 캐시가 남고 브라우저의 히스토리에 파라미터 정보가 저장되어 비교적 속도가 빠름
  • 주로 조회(Read)에 많이 사용, 데이터를 변경하지 않음
  • ?[key1]:[value1]&[key2]:[value2]&...와 같은 형식
  • ex) https://www.google.com/search ?q=%EA%B2%80%EC%83%89&oq=%EA%B2%80%EC%83%89&aqs=chrome..69i57j69i59j69i61l3.581j0j7&sourceid=chrome&ie=UTF-8

POST

  • Request Body에 데이터를 넣어서 전달 → URL에 파라미터가 보여지지 않음, 비교적 안전함(SSL 사용 시 더욱 안전함)
  • 캐시 및 파라미터 정보가 남지 않고 비교적 속도가 느림
  • 주로 입력(Create), 수정(Update), 삭제(Delete)에 많이 사용, 데이터를 변경함
  • URL에 정보를 담을 수 있는 한계를 초과하거나, 바이너리 데이터와 같은 파일 업로드를 처리하는 경우 사용

CRUD 구현

Create

  1. 폼으로 입력받은 데이터를 DB에 저장 후 다시 index 페이지로 이동

    # myapp/views.py
    
    from django.shortcuts import render, redirect
    from .models import Memo
    
    def index(request):
       return render(request, 'index.html')
    
    def createMemo(request):
       # POST 요청으로 받은 데이터 중 text라는 이름으로 넘어온 데이터를 text라는 이름의 변수에 저장
       text = request.POST['text']
    
       # Memo 테이블의 text 필드에 text 변수의 값을 넣어 레코드 생성 후 해당 레코드를 memo라는 이름의 변수에 저장
       memo = Memo(text=text)
    
       # 생성한 레코드를 DB에 저장
       memo.save()
    
       # 지정한 페이지로 이동, 주소를 직접 지정하거나 urls.py에서 지정한 name 값을 적어줌
       return redirect('index')    # = HttpResponseRedirect(reverse('index'))

Read

  1. DB에서 데이터 조회 후 템플릿에 전달

    • 모든 모델 클래스는 objects를 가짐
    • all(): 해당 모델 클래스, 즉 테이블의 모든 레코드를 반환
    # myapp/views.py
    
    ...
    
    def index(request):
       # DB에서 Memo 테이블의 모든 글을 가져옴
       memos = Memo.objects.all()
    
       # 템플릿에 전달하기 위해 데이터를 딕셔너리 타입의 context 변수에 추가, 반드시 딕셔너리 타입이어야 함
       context = {'memos': memos}
    
       # 데이터를 포함하여 템플릿 렌더링
       return render(request, 'index.html', context)
    
    ...
    
  2. 템플릿에서 데이터를 리스트로 표시

    <!-- myapp/templates/index.html -->
    <!-- 이전에 생성한 form 태그 아래에 추가 -->
    
    ...
    
    <div>
     <ul>
       {% for memo in memos %}
       <li>
         #{{ memo.id }} [{{ memo.created_at | date:'Y-m-d' }}] {{ memo.text }}
       </li>
       {% endfor %}
     </ul>
    </div>
    
    ...
    

Update

  1. urls.py에 수정 경로 추가

    • Dynamic URL: 요청 시 URL에 전달되는 파라미터 값을 변수로 사용하여 페이지를 렌더링, <[타입]:[변수명]> 형태로 작성
    # myapp/urls.py 
    # 이전에 생성한 urlpatterns에 경로 추가
    
    ...
    
    urlpatterns = [ 
       ...,
       path('update/<str:id>/', views.updateMemo, name='updateMemo'),
    ]
  2. views.py에 updateMemo 함수 추가

    • Dynamic URL로 전달받은 변수가 request와 함께 함수의 입력값으로 전달됨
    • get(): 해당 모델 클래스, 즉 테이블에서 필드 값이 지정한 값과 같은 레코드 하나만 반환 → 존재하지 않거나 둘 이상인 경우 에러 발생
    # myapp/views.py
    
    ...
    
    def updateMemo(request, id):
         # URL로 전달받은 id와 같은 id를 가진 Memo 테이블의 레코드를 찾아 memo 변수에 저장
       memo = Memo.objects.get(id=id)
    
       # GET 요청이라면, 즉 수정 페이지를 표시하라는 요청이라면
       # context에 레코드를 담아 템플릿에 전달
       if request.method == 'GET':
           context = {'memo': memo}
    
           return render(request, 'update.html', context)
    
       # POST 요청이라면, 즉 수정한 내용이 포함된 요청이라면
       # 전달 받은 text로 레코드의 text 필드 값을 대체하여 DB에 저장 후 path의 name이 index인 페이지로 이동
       elif request.method == 'POST':
           memo.text = request.POST['text']
           memo.save()
    
           return redirect('index')
    
    ...
    
  3. 템플릿에 수정 페이지로 이동하는 링크 생성

    <!-- myapp/templates/index.html -->
    <!-- 이전에 생성한 리스트 항목에 링크 추가 -->
    
    ...
    
    <div>
     <ul>
       {% for memo in memos %}
       <li>
         #{{ memo.id }} [{{ memo.created_at | date:'Y-m-d' }}] {{ memo.text }}
         <a href="./update/{{ memo.id }}/">[ 수정 ]</a>
       </li>
       {% endfor %}
     </ul>
    </div>
    
    ...
    
  4. 수정 페이지로 사용할 템플릿 생성

    <!-- myapp/templates/update.html -->
    
    <!DOCTYPE html>
    <html lang="en">
     <head>
       <meta charset="UTF-8" />
       <meta http-equiv="X-UA-Compatible" content="IE=edge" />
       <meta name="viewport" content="width=device-width, initial-scale=1.0" />
       <title>Update Memo</title>
       <style>
         form > label {
           display: inline-block;
           width: 120px;
         }
    
         form > input {
           padding: 4px;
           margin-bottom: 4px;
         }
       </style>
     </head>
     <body>
       <h1>Memo Form</h1>
    
       <form action="#" method="POST" id="updateMemoForm">
         {% csrf_token %}
         <label for="text">메모 수정</label>
         <input
           type="text"
           id="text"
           name="text"
           autocomplete="off"
           placeholder="내용을 입력하세요."
           value="{{ memo.text }}"
         />
       </form>
    
       <button type="submit" form="updateMemoForm">완료</button>
     </body>
    </html>

Delete

  1. urls.py에 삭제 경로 추가

    # myapp/urls.py 
    # 이전에 생성한 urlpatterns에 경로 추가
    
    ...
    
    urlpatterns = [ 
       ...,
       path('delete/<str:id>/', views.deleteMemo, name='deleteMemo'),
    ]
  2. views.py에 deleteMemo 함수 추가

    • delete(): 해당 레코드를 DB에서 삭제
    # myapp/views.py
    
    ...
    
    def deleteMemo(request, id):
       # URL로 전달받은 id와 같은 id를 가진 Memo 테이블의 레코드를 찾아 memo 변수에 저장
       memo = Memo.objects.get(id=id)
    
       # 해당 레코드를 DB에서 삭제
       memo.delete()
    
       # path의 name이 index인 페이지로 이동
       return redirect('index')
    
    ...
    
  3. 템플릿에 삭제 요청을 보내는 링크 생성

    <!-- myapp/templates/index.html -->
    <!-- 이전에 생성한 리스트 항목에 링크 추가 -->
    
    ...
    
    <div>
     <ul>
       {% for memo in memos %}
       <li>
         #{{ memo.id }} [{{ memo.created_at | date:'Y-m-d' }}] {{ memo.text }}
         <a href="./update/{{ memo.id }}/">[ 수정 ]</a> |
         <a href="./delete/{{ memo.id }}/">[ 삭제 ]</a>
       </li>
       {% endfor %}
     </ul>
    </div>
    
    ...
    

참고: 하나의 URL에서 GET과 POST를 모두 처리하는 방법

  • view 함수에서 분기 처리
def test(request):
    if request.method == 'GET':
        # GET 방식으로 요청한 경우 수행할 작업
    elif request.method == 'POST:
        # POST 방식으로 요청한 경우 수행할 작업

※ 강의 링크: https://inf.run/DmuA

+ Recent posts