Skip to content

Next.js

Next.js는 서버 사이드 렌더링, 정적 웹 페이지 생성 등 리액트 기반 웹 애플리케이션 기능들을 가능케 하는 Node.js 위에서 빌드된 오픈 소스 웹 개발 프레임워크이다. 리액트 문서는 Next.js를 "권고하는 툴체인들" 중 하나로 언급하며 개발자들이 Node.js로 서버 렌더링되는 웹사이트를 빌드할 때의 해결책의 하나로 충고하고 있다.

Next.js의 저작권 및 상표는 Vercel이 소유하며, Vercel은 오픈 소스 개발을 유지하고 주도한다.

배경

Next.js는 서버 측 렌더링 및 정적 웹사이트 생성을 포함한 여러 추가 기능을 지원하는 리액트 (자바스크립트 라이브러리) 프레임워크이다. 리액트는 전통적으로 자바스크립트를 사용하여 클라이언트 브라우저에서 렌더링되는 웹 애플리케이션을 구축하는 데 사용되는 자바스크립트 라이브러리이다. 그러나 개발자는 자바스크립트에 액세스할 수 없거나 자바스크립트를 비활성화한 사용자에게 서비스를 제공하지 못함, 잠재적인 보안 문제, 페이지 로딩 시간이 크게 연장됨, 사이트의 전체 검색 엔진 최적화에 해를 끼치는 등 이 전략의 여러 가지 문제를 인식하고 있다. Next.js와 같은 프레임워크는 웹 사이트의 일부 또는 전체가 클라이언트로 전송되기 전에 서버 측에서 렌더링되도록 허용하여 이러한 문제를 회피한다. Next.js는 리액트의 가장 인기 있는 프레임워크 중 하나이다. 이는 새 앱을 시작할 때 사용할 수 있는 몇 가지 권장 "도구 체인" 중 하나이며, 모두 일반적인 작업을 지원하는 추상화 계층을 제공한다. Next.js에는 Node.js가 필요하며 노드 패키지 관리자를 사용하여 초기화할 수 있다.

구글은 2019년에 43개의 풀 리퀘스트를 포함하여 Next.js 프로젝트에 기여했다. 2022년 3월 현재 이 프레임워크는 월마트, 애플, 나이키, 넷플릭스, 틱톡, 우버, Lyft, 스타벅스를 포함한 많은 대규모 웹사이트에서 사용된다. 2020년 초, Vercel은 소프트웨어 개선을 지원하기 위해 시리즈 A 자금에서 2,100만 달러를 확보했다. 프레임워크의 원저자인 기예르모 라우흐(Guillermo Rauch)는 현재 Vercel의 CEO이고 프로젝트의 수석 유지관리자는 팀 뉴트켄스(Tim Neutkens)이다.

Categories

Routing

Data Fetching

NextJS:Rendering

Data Fetching

  • NextJS:getStaticProps
  • NextJS:getStaticPaths
  • NextJS:getServerSideProps
  • NextJS:IncrementalStaticRegeneration
  • NextJS:ClientSideFetching
  • NextJS:BuildingForms

Styling

Optimizing

  • NextJS:Images
  • NextJS:Fonts
  • NextJS:Scripts
  • NextJS:StaticAssets
  • NextJS:LazyLoading
  • NextJS:Analytics
  • NextJS:OpenTelemetry
  • NextJS:Instrumentation
  • NextJS:Testing
  • NextJS:Configuring

File Conventions

  • NextJS:default.js
  • NextJS:error.js
  • NextJS:layout.js
  • NextJS:loading.js
  • NextJS:not-found.js
  • NextJS:page.js
  • NextJS:route.js
  • NextJS:RouteSegmentConfig
  • NextJS:template.js
  • NextJS:MetadataFiles
    • NextJS:Icons - favicon, icon, and apple-icon
    • NextJS:opengraph-image
    • NextJS:twitter-image
    • NextJS:robots.txt
    • NextJS:sitemap.xml
  • NextJS:next-env.d.ts

with Supabase

ETC

Functions

Boilerplate

Next.js Enterprise Boilerplate
https://github.com/Blazity/next-enterprise
고성능, 유지보수 용이한 엔터프라이즈급 Next.js 템플릿
  • 고성능, 유지보수 용이한 엔터프라이즈급 Next.js 템플릿
  • Tailwind CSS + ESlint + Prettier + Jest + Playwright + Storybook
  • Observability / Health Check / Commit Git Hook / GitHub Actions 지원
  • ChatGPT 기반 자동화된 코드 리뷰
  • 완벽한 Lighthouse 점수

Terms

Hydrate
Next.js의 Hydrate란? :: 이뇽의세상
Hydrate는 Server Side 단에서 렌더링 된 정적 페이지와 번들링된 JS파일을 클라이언트에게 보낸 뒤, 클라이언트 단에서 HTML 코드와 React인 JS코드를 서로 매칭 시키는 과정을 말한다.

How to start

npm install -g create-next-app
npx create-next-app my-app

TypeScript app

npx create-next-app@latest --ts
# or
yarn create next-app --typescript
# or
pnpm create next-app --ts

환경 변수

env 파일은 다음과 같은 규칙을 사용한다.

  • .env.local (로컬 개발 시)
  • .env.development
  • .env.production (서버 배포 시)
  • .env.test

React의 경우 REACT_APP_, Nextjs는 NEXT_PUBLIC_를 사용해야 브러우저에서도 환경변수가 노출된다. 그렇지 않으면 Node.js 환경에서만 노출된다.

단, 동적 조회는 인라인되지 않는다.

// This will NOT be inlined, because it uses a variable
const varName = 'NEXT_PUBLIC_ANALYTICS_ID'
setupAnalyticsService(process.env[varName])

// This will NOT be inlined, because it uses a variable
const env = process.env
setupAnalyticsService(env.NEXT_PUBLIC_ANALYTICS_ID)

CDK Amplify에서의 Private Gitlab 연동

Private Gitlab 저장소에서 AWS CDK를 사용해 AWS Amplify 호스팅하는 방법.

CDK에 정의되어 있는 GitLabSourceCodeProvider 클래스를 사용하면 Amplify가 깃랩에 있는 저장소를 읽어서 소스를 가져온다. 예제가 나와있는데, owner, repository, oauthToken을 필수로 입력받도록 되어 있다.

/**
 * #### GitLabSourceCodeProvider Example (CDK v2 / typescript) ####
 */
const amplifyApp = new amplify.App(this, "MyApp", {
  sourceCodeProvider: new amplify.GitLabSourceCodeProvider({
    owner: "<user>",
    repository: "<repo>",
    oauthToken: SecretValue.secretsManager("my-gitlab-token"),
  }),
});

owner을 입력할 때, 프로젝트가 Gitlab 그룹에 포함되어있다면 그룹명을 적어야한다. 예를 들어 gitlab.com/mygroup/저장소네임으로 되어있다면 mygroup을 적어야하는데, allssu만 입력해놓은 다거나 숫자로 되어있는 사용자 ID를 입력하고 repository만 변경해가면서 테스트하다가 한참 시간을 보냈다. owner에 유저이름이나 ID를 입력해봤자 프로젝트를 찾지 못한다는 404 에러가 뜬다. 프로젝트를 사용자 밑에 둔다면 사용자명을 입력하면 되겠지만, 그룹 밑에 둔다면 owner는 그룹명이 들어가야 한다. 이거 때문에 한참을 해메게 되었는데, CDK 문서에는 외부 서비스라 그런지 생각보다 친절하게 적혀있진 않았다. 😭

repository의 경우 저장소 이름 그대로 적으면 된다.

마지막으로 oauthToken을 입력하기 위해 Secrets Manager을 사용하는 예제를 보여주는데, CDK에서 Secrets Manager을 만들면 깔끔하게 연동할 수 있다.

Gitlab 엑세스 토큰과 Secrets Manager 생성하기

보안 암호를 입력하지 않고, CDK를 통해 Secrets Manager을 프로비저닝하는 스택 코드는 다음과 같다:

/**
 * #### Secrets Manager Stack (CDK v2 / typescript) ####
 *
 * /lib/secretsmanager-stack.ts
 */
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { aws_secretsmanager as secretsmanager } from "aws-cdk-lib";

export class SecretsmanagerStack extends cdk.Stack {
  public readonly secret: secretsmanager.Secret; // 다른 스택으로 Secret을 전달시키기 위한 클래스 속성
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    this.secret = new secretsmanager.Secret(this, "Secret", {});
  }
}

CDK를 통해 배포하면 보안 암호가 난수로 생성이 되는데, AWS Management Console에 직접 들어가서 Gitlab Personal Access Token을 변경했다. CDK 소스에 암호가 포함되지 않고 더욱 안전하게 관리된다.

Amplify 스택에서 Secrets Manager 연동하기

Amplify에서는 보안 암호를 받을 수 있도록, StackProps를 상속한 인터페이스를 생성해준다. 이렇게 하면 보안 암호 값이 포함되어있는 보안 암호 이름(식별자)도 노출하지 않고 연동할 수 있게 된다.

/**
 * #### Amplify Stack (CDK v2 / typescript) ####
 *
 * /lib/amplify-stack.ts
 */
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as amplify from "@aws-cdk/aws-amplify-alpha";
import { SecretValue } from "aws-cdk-lib";

interface StackProps extends cdk.StackProps {
  secret: cdk.aws_secretsmanager.Secret;
}

export class AmplifyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);

    new amplify.App(this, "amplify", {
      appName: "MyApp",
      sourceCodeProvider: new amplify.GitLabSourceCodeProvider({
        owner: "group", // 프로젝트가 깃랩 그룹에 포함되어있다면, 그룹 이름을 적어야 한다.
        repository: "app",
        oauthToken: SecretValue.secretsManager(props.secret.secretName), // 전달받은 Secret Name
      }),
    });
  }
}

Secrets Manager 스택과 Amplify 스택은 서로 값을 주고받을 수 있게 되었고, CDK의 시작점인 /bin에서 Amplify 스택에 Secret을 전달했다.

/**
 * #### 스택 연결 및 생성 (CDK v2 / typescript) ####
 *
 * /bin/cdk.ts
 */
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { CognitoStack } from "../lib/cognito-stack";
import { AmplifyStack } from "../lib/amplify-stack";
import { SecretsmanagerStack } from "../lib/secretsmanager-stack";

const app = new cdk.App();

const { secret } = new SecretsmanagerStack(app, "SecretsmanagerStack", {
  description: "Amplify Secrets",
});

new AmplifyStack(app, "AmplifyStack", {
  secret,
  description: "Next.js 13 App Amplify",
});

서버-사이드 에서 헤더 목록 읽기

다음과 같이 헤더 전체 목록을 읽을 수 있다.

import {headers} from 'next/headers';

function getHeadersRecord() {
  const result = {} as Record<string, string>;
  const headersList = headers();
  headersList.forEach((value, key) => {
    result[key] = value;
  });
  return result;
}

Sample

대충 결과는 다음과 비슷하다.

 header[host]=localhost:3000
 header[user-agent]=Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0
 header[accept]=*/*
 header[accept-language]=en-US,en;q=0.5
 header[accept-encoding]=gzip, deflate, br
 header[referer]=http://localhost:3000/settings/profile
 header[next-url]=/en/settings/profile
 header[dnt]=1
 header[connection]=keep-alive
 header[cookie]=isNonPlugin=1; proj_lng=ko; proj-lng=en;
 header[sec-fetch-dest]=empty
 header[sec-fetch-mode]=cors
 header[sec-fetch-site]=same-origin
 header[x-forwarded-host]=localhost:3000
 header[x-forwarded-port]=3000
 header[x-forwarded-proto]=http
 header[x-forwarded-for]=::ffff:127.0.0.1

Troubleshooting

window is not defined error

이 에러가 발생하는 이유는 서버사이드에서 호출이 일어났기 때문이다.

SSR을 지원하는 nextjs가 새로고침이 될때 저 부분을 import 하게 되는데, ssr에서는 window 객체가 없으므로 not defined가 뜨는 것이다.

이를 해결하기 위해 nextjs에서 제공하는 함수가 있다. 바로 dynamic 이라는 함수다.

다음과 같이 Import 하면 된다.

import dynamic from 'next/dynamic'
const ApexChart = dynamic(() => import("react-apexcharts"), { ssr: false });

The Middleware "pages/middleware" must export a middleware or a default function

루트 페이지의 middleware.ts의 미들웨어 함수 이름을 "middleware" 로 하거나 export default 하면 된다.

export async function middleware(req: NextRequest) {
  // ...
}

Largest Contentful Paint (LCP)

warn-once.js:16 Image with src "/_next/static/media/proj.run.aac09183.svg" was detected as the Largest Contentful Paint (LCP). Please add the "priority" property if this image is above the fold.
Read more: https://nextjs.org/docs/api-reference/next/image#priority

Image with src "/_next/static/media/..." has either width or height modified, but not the other

warn-once.js:16 Image with src "/_next/static/media/proj.run.aac09183.svg" has either width or height modified, but not the other. If you use CSS to change the size of your image, also include the styles 'width: "auto"' or 'height: "auto"' to maintain the aspect ratio.

Prevent client components from being async functions

./app/[lng]/page.tsx
8:1  Warning: Prevent client components from being async functions. See: https://nextjs.org/docs/messages/no-async-client-component  @next/next/no-async-client-component

Promise 지원은 React Server Component RFC에 따라 클라이언트 구성 요소는 비동기 기능이 될 수 없습니다.

Possible Ways to Fix It:

  • Remove the async keyword from the client component function declaration, or
  • Convert the client component to a server component

Error: No response is returned from route handler

 ⨯ Error: No response is returned from route handler '/home/your/Project/web/app/api/auth/login/route.ts'. Ensure you return a `Response` or a `NextResponse` in all branches of your handler.

Consider adding an error boundary to your tree to customize error handling behavior

See also

Favorite site

Tutorials

Guide

with Cloud Deploy