NextJS15

MM·2025년 1월 18일

ArticleDigest

목록 보기
6/12
post-thumbnail

🔴 NextJS의 이점

이미지 최적화 - Image 컴포넌트

표준 HTML img 태그를 확장한 것.

  • 이미지를 자동으로 최적화
  • 동일 소스 경로의 이미지는 자동으로 캐싱
  • 서버 사이드에서 자동으로 이미지 크기를 조정하고, 최적의 포맷을 선택하여 불필요한 데이터 전송을 줄인다
  • lazy loading 기본 지원

레이아웃 이동 최소화

이미지에 명확한 크기(width와 height 속성)를 지정
-> 브라우저가 페이지 로드 전에 적절한 공간을 확보
-> 이미지 로딩 후 레이아웃의 변동을 방지
-> CLS(시각적 안정성)점수 상승!


property

loader

로딩 함수를 지정하여 이미지 URL을 동적으로 생성해주는 속성.

import Image from 'next/image'

const myLoader = ({ src, width, quality }) => {
  return `https://example.com/${src}?w=${width}&q=${quality}`
}

export default function MyPage() {
  return (
    <Image
      loader={myLoader}
      src="logo.png"
      alt="회사 로고"
      width={500}
      height={300}
      quality={90}
    />
  )
}

fill

이미지가 부모 컨테이너를 완전히 채우게 하는 속성.

import Image from 'next/image'

export default function ResponsiveImage() {
  return (
    <div style={{ position: 'relative', width: '100%', height: '500px' }}>
      <Image
        src="/static/images/logo.png"
        alt="회사 로고"
        fill
        style={{ objectFit: 'cover' }} //비율 유지
      />
    </div>
  )
}

loading

  • Lazy 로딩: 이미지가 뷰포트에 근접할 때까지 로딩 지연
  • Eager 로딩: 이미지를 페이지 로드 즉시 로딩
  • LazyOnload: 특정 상호작용(예: 탭 전환)이 발생한 후 이미지 로딩

placeholder와 blurDataURL

  • placeholder: 이미지가 로드되는 동안 표시할 임시 이미지
  • blurDataURL: blur를 사용하는 경우 흐릿한 이미지의 데이터 URL을 제공
import Image from 'next/image'

export default function Home() {
  return (
    <Image
      src="/static/images/logo.png"
      alt="로고"
      placeholder="blur"
      blurDataURL="data:image/png;base64,..."
      width={300}
      height={100}
    />
  )
}

sizes

미디어 쿼리를 활용하여 브라우저가 화면 크기에 적절한 크기 및 포맷을 선택하여 로드한다.

import Image from 'next/image'

export default function ResponsiveImage() {
  return (
    <div>
      <Image
        src="/static/images/logo.png"
        alt="반응형 로고 이미지"
        width={1000}
        height={600}
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
        layout="responsive"
      />
    </div>
  );
}

sizes와 pictures의 차이

특징Next.js <Image> + sizesHTML <picture>
최적화 지원Next.js가 자동으로 이미지 최적화 및 로딩 처리수동으로 최적화된 이미지를 제공해야 함
사용 편의성단일 태그로 설정 간단, Next.js 기능과 연동더 복잡한 HTML 구조 필요
호환성Next.js 프로젝트에서만 사용 가능모든 브라우저에서 동작
구체적인 제어sizes 속성으로 간단히 크기 힌트 제공<source>로 다양한 이미지 파일 선택
다중 형식 지원 (webp 등)기본적으로 지원하지 않음<source>로 형식 교체 가능

priority

import Image from 'next/image';
import { useEffect } from 'react';

function OptimizedPage() {
  useEffect(() => {
    // 페이지가 로드된 후 추가적인 자원을 동적으로 로드
    const script = document.createElement('script');
    script.src = "https://example.com/additional-feature.js";
    script.async = true;
    document.body.appendChild(script);
  }, []);

  return (
    <div>
      <h1>페이지 최적화 예시</h1>
      <Image
        src="/path/to/image.jpg"
        alt="Optimized Image"
        width={640}
        height={480}
        priority //해당 속성을 사용하면 중요한 이미지를 먼저 로드 가능
      />
    </div>
  );
}

이벤트 처리

onLoadingComplete, onLoad, onError

function handleLoadingComplete({naturalWidth, naturalHeight}) {
  console.log(`로딩 완료: 너비 ${naturalWidth}, 높이 ${naturalHeight}`);
}

<Image
  src="/static/images/logo.png"
  alt="로고"
  onLoadingComplete={handleLoadingComplete}
  onLoad={() => console.log('이미지가 로드되었습니다.')}
  onError={() => console.log('이미지 로드에 실패했습니다.')}
  width={500}
  height={300}
/>

Next.config.js를 통한 글로벌 설정

loaderFile

Next.js의 이미지 최적화 기능을 사용자 정의 로더로 대체

remotePatterns

외부 이미지 소스의 사용을 제한
-> 서버에 허용된 출처에서만 이미지 로드 가능

imageSizes

이미지가 다양한 디스플레이 크기에서 어떻게 보여질지 결정하는 데 사용

// next.config.js
module.exports = {
  images: {
    loader: 'custom',
    loaderFile: './custom-image-loader.js',
    remotePatterns: [
      {protocol: 'https', hostname: 'example.com'}
    ],
    images: {
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384]
  },
  },
}

dangerouslyAllowSVG

SVG는 XSS 공격과 같은 보안 문제를 야기할 수 있다!

dangerouslyAllowSVG를 활성화하면 Next.js는 SVG 이미지에 대한 최적화를 시도한다.
-> 추가적인 보안 조치를 취하는 것이 좋음!

// next.config.js
module.exports = {
  images: {
    dangerouslyAllowSVG: true,
    contentSecurityPolicy: "default-src 'self'; img-src 'self' data: https:;"
  },
}

원격 이미지 처리

next.config.js에 도메인을 등록한 후 사용

module.exports = {
  images: {
    domains: ['example.com'],
  },
}



폰트 성능 개선 - next/font

Next.js는 폰트 로딩 시점을 최적화하여 초기 로드 시 폰트가 페이지 렌더링을 방해하지 않도록 관리한다!

지능적 로딩 방식

자체적으로 폰트를 최적화하고 필요한 폰트만 불러오는 로딩 방식.

외부 폰트 자체 호스팅

기본적으로 모든 Google Fonts를 자체 호스팅으로 지원
-> 폰트 파일을 사전에 다운로드하고 프로젝트에 포함시켜 브라우저가 폰트 파일을 캐시
-> 즉, 구글 API로 별도 요청을 전송하지 않는다!

효율적인 폰트 포맷 사용

최신 폰트 포맷(WOFF2)을 사용하여 파일 크기를 최소화하고 로딩 속도를 최적화




스크립트 로딩 및 실행 최적화 - Script 컴포넌트

Script 컴포넌트는 스크립트를 효율적으로 관리하여 페이지 로딩 시간에 미치는 영향을 최소화한다!

스크립트 최적화

동적 임포트

next/dynamic을 사용한 동적 임포트로 컴포넌트를 비동기적으로 로드

코드 분할

코드를 분할하여 사용자가 필요한 순간에만 스크립트를 로드


property

strategy

로드 방식설명주요 사용 사례
beforeInteractive페이지의 상호작용 가능한 요소들이 로드되기 전 스크립트 로드필수적인 스크립트 로드가 사용자 경험을 방해하지 않도록 할 때 사용
afterInteractive사용자와의 첫 상호작용 이후 스크립트 로드페이지 로딩에 영향을 덜 미치는 비필수 스크립트 로드에 사용
lazyOnload페이지가 완전히 로드된 후, 브라우저가 유휴 상태가 되었을 때 스크립트 로드꼭 필요하지 않은 기능의 스크립트 로드에 사용
import Script from 'next/script';

function Home() {
  return (
    <div>
      <Script
        src="https://example.com/essential-script.js"
        strategy="beforeInteractive" //필수 스크립트는 초기에 로드
      />
      <Script
        src="https://example.com/non-essential-script.js"
        strategy="lazyOnload" //중요하지 않은 스크립트는 나중에 로드
        onLoad={() => console.log('Non-essential Script loaded')}
      />
      <p>Next.js 홈페이지 예시</p>
    </div>
  );
}

export default Home;



원리

이동하는 페이지를 전체 새로고침하는 것이 아닌, 최적화된 번들만 일부 로드한다!

미리 가져오기

미리 가져오기 기능은 제품(Production) 모드에서만 사용 가능!

Link 컴포넌트는 prefetch 옵션을 통해 뷰포트에 보여질 때, 연결된 경로의 데이터를 미리 가져와 탐색 성능을 크게 향상시킨다.

  • null(기본값)
    • 정적 경로 -> 모든 하위 경로 가져옴
    • 동적 경로 -> loading.tsx가 있는 가장 가까운 세그먼트까지 가져옴
  • true: 정적 경로와 동적 경로 모두 미리 가져옴
  • false: 안 가져옴
export default function Links() {
  return (
    <>
      <Link href={someLink}>null</Link>
      <Link prefetch={true} href={someLink}>true</Link>
      <Link prefetch={false} href={someLink}>false</Link>
    </>
  )
}

useRouter도 prefetch를 사용할 수 있다!

  const router = useRouter()
  useEffect(() => {
    router.prefetch('/movies')
  }, [router])



🔴 SEO

NextJS 기본 지원

기능설명
구조화된 데이터 마크업next-seo 패키지를 사용하여 검색 엔진이 콘텐츠의 맥락을 이해하도록 도와줌.
사이트맵 생성sitemap 패키지를 사용하여 검색 엔진이 웹 페이지를 효율적으로 검색하고 색인 생성하도록 도움.
페이지 매김 처리next/link 구성 요소와 rel="next/prev" 속성을 사용하여 페이지 매김을 지원.
이미지 최적화이미지 최적화는 페이지 로드 시간을 줄이고, SEO에 긍정적인 영향을 미침.
페이지 공유 카드 구현next-seo 패키지를 사용하여 Open Graph 및 Twitter 카드 기능을 구현할 수 있음.

SEO를 위한 서버 측 렌더링(SSR)

getServerSideProps

서버측 렌더링 프로세스 중에 데이터를 가져와 페이지에 소품으로 전달
-> 검색 엔진이 페이지의 전체 콘텐츠를 볼 수 있다!

export async function getServerSideProps(context) { 
  const res = await fetch('https://api.example.com/data'); 
  const data = await res.json();
  return { props: { data }, };
}

메타데이터

정적 경로에서 메타데이터 생성

layout.tsx 혹은 page.tsx 에서 metadata 객체를 내보s내기

import Header from '@/components/Header'
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: '제목!',
  description: '설명..',
  openGraph: {
    title: '제목',
    // ...
  },
  twitter: {
    title: '제목',
    // ...
  }
}

동적 경로에서 메타데이터 생성

generateMetadata 함수

generateMetadata함수는 페이지와 같은 인수를 받아서 처리
-> API 요청으로 생성한 메타데이터를 가져올 수 있다!

//app/movies/[movieId]/page.tsxTSX
import type DetailedMovie from '@/stores/movies'

function fetchMovie(id, plot) {
  const res = await fetch(`https://omdbapi.com/i=${id}&plot=${plot}`)
  return await res.json()
}

//해당 함수를 사용하여 param으로 넘어온 값을 사용해 메타데이터를 만들 수 있음!
export async function generateMetadata({
  params,
  searchParams
}) {
  const movie = await fetchMovie(params.movieId, searchParams.plot)
  
  return {
    title: movie.Title,
    description: movie.Plot,
  }
}

export default async function MovieDetails({
  params,
  searchParams
}) {
  const movie = await fetchMovie(params.movieId, searchParams.plot)
  return (
    <>
      <h1>{movie.Title}</h1>
      <p>{movie.Plot}</p>
    </>
  )
}

title 템플릿

title을 객체 타입으로 지정해 템플릿(template)과 기본값(default)을 제공할 수 있다!
-> %s 치환 문자에 동적으로 값 삽입됨
(%s에 넣고 싶은 부분은 어디다 쓰는거지..?)

//app/layout.tsxTSX
import Header from '@/components/Header'
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: { 
    template: '%s | 사이트이름', 
    default: '사이트이름'
  },
  description: '설명..',
}



🔴NextJS 15

업데이트 사항

기능설명
비동기 요청 API데이터 요청 없는 컴포넌트, 주요 API(params, searchParams)를 비동기로 처리하여 초기 로드 속도 향상.
캐싱 기본값 변경기본 캐싱 설정을 해제하여 라이브러리의 캐싱 기능과 중복되지 않음. force-static 옵션으로 재캐싱 가능.
Form 컴포넌트form 요소를 확장한 최적화 컴포넌트로 제출 경로를 프리패칭하고 제출 데이터를 쿼리스트링으로 보존.
자체 호스팅 개선캐시 제어 헤더에 대한 더 많은 제어 기능 추가, 이미지 최적화 성능 향상.
보안 강화서버 액션이 공개 엔드포인트가 되는 것을 방지하기 위한 기능 추가.
빌드 및 개발 성능 개선정적 페이지 생성 최적화, 서버 컴포넌트의 HMR 기능 강화.



🔴 세팅

eslintrc.json

{
  "extends": [
    "next/core-web-vitals", 
    "next/typescript", 
    "prettier"
  ]
}

prettierrc.JSON

{
  "semi": false,
  "singleQuote": true,
  "singleAttributePerLine": true,
  "bracketSameLine": true,
  "endOfLine": "lf",
  "trailingComma": "none",
  "arrowParens": "avoid",
  "plugins": ["prettier-plugin-tailwindcss"]
}

.vscode/settings.json

{
  "[javascript]": {
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascriptreact]": {
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}



클라이언트 컴포넌트와 서버 컴포넌트

클라이언트 컴포넌트

클라이언트 컴포넌트 또한 일부 정적 요소는 서버에서 렌더링한다!

  • 상호작용('click', 'load' 이벤트 등) 가능
  • 브라우저 API(window, document 등) 활용 가능

서버 컴포넌트

  • 보안
  • 캐싱
  • 성능 및 SEO

서버사이드에서의 비동기 컴포넌트 스트리밍

export default async function Xyz() {
  await wait(3000) //이렇게 하면 서버사이드에서도 timeout처럼 사용가능
  return <h2>Xyz 컴포넌트!</h2>
}



🔴 라우팅

명시적 컴포넌트 계층 구조(Component Hierarchy)

파일명설명
error에러 페이지
layout고정 레이아웃
loading로딩 페이지
not-found찾을 수 없는(404) 페이지
page기본 페이지
template변화 레이아웃(탐색 시)
default
global-error
route

error

error.tsx는 클라이언트 컴포넌트여야 한다

클라이언트에서 발생하는 에러 상황까지 처리하기 위해서!
ex) 사용자 입력의 유효성 검사, 잘못된 API 요청..



고급 라우팅 패턴

경로 그룹

소괄호를 사용해 URL 경로에 영향을 주지 않는 그룹을 만들 수 있다.
-> 각자의 레이아웃(layout.tsx)을 가질 수 있다!

│ │ ├─movies/
│ │ │ ├─[movieId]/
│ │ │ │ └─page.tsx
│ │ └─layout.tsx <== (movies) 그룹에서만 동작하는 레이아웃
│ ├─layout.tsx <== 루트 레이아웃
│ └─page.tsx


경로 병렬 처리(Parallel Routes)

@ 접두사의 폴더는 URL 경로에 영향을 주지 않는 페이지
-> 하나의 레이아웃에서 병렬로 경로 처리 가능
-> 여러 페이지나 컴포넌트를 병렬로 로드 및 렌더링 가능
-> 순차적 로딩 대비 전체 로딩 시간이 단축
-> 각 컴포넌트의 로딩 상태 독립적으로 표시 가능

├─app
│ ├─async
│ │ ├─@abc <==병렬 처리된다
│ │ │ ├─loading.tsx
│ │ │ └─page.tsx
│ │ ├─@xyz <==병렬 처리된다
│ │ │ ├─loading.tsx
│ │ │ └─page.tsx
│ │ ├─layout.tsx
│ │ ├─loading.tsx
│ │ └─page.tsx


경로 가로채기(Intercepting Routes)

적용되지 않으면 .next 폴더 삭제 후 서버 재시작하기

특정 URL에 접근했을 때 기존 페이지가 아닌, 지정된 다른 UI나 레이아웃을 표시할 수 있다.
-> 경로 가로채기를 사용하면 특정 하위 경로가 아닌 다른 경로에서도 상위 레이아웃을 재사용할 수 있음.

파일명에 (.)온점이 하나 추가될 때마다 상위로 올라간다.

폴더 경로URL설명
/app/a/b/(.)x/a/b/x같은 레벨 세그먼트
/app/a/b/(..)x/a/x상위 레벨 세그먼트
/app/a/b/(...)x/x루트 레벨 세그먼트

├─app
│ ├─a
│ │ └─b
│ │ └─c
│ │ ├─(...)x <======1번x
│ │ │ └─page.tsx
│ │ │ └─layout.tsx <===1번 layout
│ │ └─page.tsx
│ └─x <========2번x
│ └─page.tsx
│ └─layout.tsx <======2번 layout

경로 가로채기로 url이 /x일때 a,b,c가 모두 렌더링되고 1번 x가 보임!

미들웨어로 가능한데 경로 가로채기를 쓰는 이유

구분미들웨어 경로 가로채기
처리 위치서버 측클라이언트 측
실행 시점요청이 서버로 전달되기 전에 실행프론트엔드 컴포넌트 렌더링 시점에서 실행
목적서버의 URL 리디렉션, 인증, 헤더 수정 등 사전 처리URL 해석 후 페이지 및 레이아웃 렌더링 제어
주요 처리리디렉션, 인증, 헤더 수정 등 요청 처리페이지 렌더링 방식, 동적 로딩, 경로 기반 최적화



🔴 미들웨어

주의사항

  • 미들웨어는 호출이 끝나야 경로 접근이 가능 -> 복잡하거나 오래 걸리는 작업 피하기
  • 쿠키나 헤더는 미들웨어 실행 후에만 수정 가능!

라이브러리

path-to-regexp

복잡한 경로 매칭 처리에 사용하면 좋다!

//middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { match } from 'path-to-regexp'

const getSession = async () => {
  return false // 임시 데이터 반환
}
const matchersForAuth = [
  '/dashboard{/*path}',
  '/myaccount{/*path}',
  '/settings{/*path}'
]

// 경로 일치 확인 함수. 여기서 path-to-regexp 라이브러리의 match를 쓴다
function isMatch(pathname: string, urls: string[]) {
  return urls.some(url => !!match(url)(pathname))
}

// 미들웨어 함수
export async function middleware(request: NextRequest) {
  // 인증이 필요한 페이지 접근 제어!
  if (isMatch(request.nextUrl.pathname, matchersForAuth)) {
    return (await getSession())
      ? NextResponse.next()
      : NextResponse.redirect(new URL('/signin', request.url))
  }
  return NextResponse.next()
}



🔴 API

api폴더에서는 page.tsx 등의 기본 파일 규칙이 아닌, route.ts 파일을 사용한다!

/app/api 폴더 내 구조를 통해 서버리스 API 엔드포인트 정의 가능

├─app
│ ├─api
│ │ ├─movies
│ │ │ └─[movieId]
│ │ │ └─route.ts
│ │ └─users
│ │ └─route.ts

//app/api/movies/[movieId]/route.tsTS
import type { NextRequest } from 'next/server'

type Context = {
  params: { movieId: string }
}

export async function GET(request: NextRequest, context: Context) {
  const { movieId } = context.params // 동적 경로
  const res = await fetch(`https://omdbapi.com/?apikey=7035c60c&i=${movieId}`)
  const data = await res.json()
  return Response.json(data)
}



🔴 서버 액션

서버에서만 실행되는 함수.
서버, 클라이언트 컴포넌트 둘 다 서버 액션(함수)를 가져와 사용할 수 있다!

//serverActions/index.ts

'use server'
export async function wait(duration = 1000): Promise<{ message: string }> {
  console.log(`Run 'wait' function`)
  return new Promise(resolve =>
    setTimeout(() => resolve({ message: `Waited for ${duration}ms` }), duration)
  )
}

주요 기능

form 태그 연동 및 폼 제출 처리

form태그의 action 속성에 서버 액션을 넣으면..
-> 서버 액션은 FormData 객체를 자동으로 받아 처리한다!

비동기 처리

서버측에서 비동기로 실행됨
-> 서버와 클라이언트 간 통신 동안 사용자 인터페이스가 멈추지 않는다!


전달 인자

직렬화

서버 액션에서 사용되는 인자와 반환 값은 React에 의해 직렬화 가능해야 한다!
ex)문자열, 숫자, 날짜, 배열..

추가 인자 전달 방법

// 클라이언트 컴포넌트
import { updateProfile } from './actions';

function UserProfile({ userId }) {
  const handleUpdate = updateProfile.bind(null, userId); //바인딩 함수를 사용하여 추가 가능

  return (
    <form action={handleUpdate}>
      <textarea name="bio"></textarea>
      <button type="submit">업데이트</button>
    </form>
  );
}

캐싱 및 데이터 재검증

데이터 변형 후 클라이언트에 최신 정보를 반영하기 위해서는 데이터 캐시를 재검증해야 한다!

revalidatePath

특정 경로의 최신 데이터를 생성하여 캐시를 갱신하는 함수

'use server';
import { revalidatePath } from 'next/server';

export async function updateProfile(formData) {
  // 프로필 업데이트 로직
  await userProfile.update(formData);
  
  // 업데이트된 프로필 페이지 캐시 재검증
  await revalidatePath('/profile');
  
  return { success: true, message: '프로필 업데이트 완료' };
}

보안

CSRF 공격 방지

// next.config.js에서 안전한 출처 설정
module.exports = {
  serverActions: {
    allowedOrigins: ['https://trusteddomain.com'],
  },
}

동일 출처 정책(Same-Origin Policy) 강화

서버 액션 호출시 Origin 헤더와 Host 헤더가 일치하지 않는 경우 요청 거부




🔴 환경변수

환경변수는 기본적으로 서버 컴포넌트에서만 접근 가능
-> NEXTPUBLIC~~로 명명시 클라이언트 컴포넌트에서 접근 가능

주의사항

보안이 요구되는 API 키 등의 중요한 정보는 서버에서만 사용하기!

환경변수 타입

환경변수 자동완성을 위해 사용

//types/env.d.tsTS
export declare global {
  namespace NodeJS {
    interface ProcessEnv {
      OMDB_API_KEY_KEY: string
      NEXT_PUBLIC_BASE_URL: string
      NEXT_PUBLIC_SITE_NAME: string
    }
  }
}



정적 사이트 생성(SSG)

빌드 시점에 HTML을 미리 생성해 빠른 배포 및 성능 제공.

Fast Refresh 지원

개발 중 실시간 코드 변경 반영.

Pre-rendering

클라이언트에서 로드되기 전에 페이지의 초기 상태를 미리 렌더링.

Built-in Webpack & Babel 설정

기본 설정으로 코드 트랜스파일 및 번들링.

Incremental Static Regeneration (ISR)

일부 정적 페이지를 런타임에 재생성.

앱 크기 자동 분석

페이지별 번들 크기 분석 도구 제공.




🔴 서버 사이드 렌더링(SSR)

HTML을 서버에서 미리 렌더링하여 초기 로드 속도 향상.
-> 브라우저가 처리해야 할 스크립트 양을 줄여 더 빠르게 인터랙티브 상태가 된다.

profile
중요한 건 꺾여도 그냥 하는 마음

0개의 댓글