한국어

Next.js 앱 라우터의 파일 기반 라우팅에 대한 심층 가이드로 그 강력한 기능을 활용해 보세요. 애플리케이션 구조화, 동적 라우트 생성, 레이아웃 처리 방법 등을 배워보세요.

Next.js 앱 라우터: 파일 기반 라우팅 종합 가이드

Next.js 13에서 도입되어 이후 버전에서 표준이 된 Next.js 앱 라우터는 우리가 애플리케이션을 구조화하고 탐색하는 방식을 혁신합니다. 이는 개발을 단순화하고 성능을 개선하며 전반적인 개발자 경험을 향상시키는 강력하고 직관적인 파일 기반 라우팅 시스템을 도입합니다. 이 종합 가이드에서는 앱 라우터의 파일 기반 라우팅을 심층적으로 다루어, 여러분이 견고하고 확장 가능한 Next.js 애플리케이션을 구축하는 데 필요한 지식과 기술을 제공할 것입니다.

파일 기반 라우팅이란 무엇인가요?

파일 기반 라우팅은 애플리케이션의 라우트 구조가 파일 및 디렉토리의 구성에 의해 직접 결정되는 라우팅 시스템입니다. Next.js 앱 라우터에서는 `app` 디렉토리 내에 파일을 생성하여 라우트를 정의합니다. 각 폴더는 라우트 세그먼트를 나타내며, 해당 폴더 내의 특수 파일들은 그 라우트 세그먼트가 어떻게 처리될지를 정의합니다. 이 접근 방식은 여러 가지 이점을 제공합니다:

앱 라우터 시작하기

앱 라우터를 사용하려면 새로운 Next.js 프로젝트를 생성하거나 기존 프로젝트를 마이그레이션해야 합니다. Next.js 버전 13 이상을 사용하고 있는지 확인하세요.

새 프로젝트 생성:

다음 명령어를 사용하여 앱 라우터가 포함된 새로운 Next.js 프로젝트를 생성할 수 있습니다:

npx create-next-app@latest my-app --example with-app

기존 프로젝트 마이그레이션:

기존 프로젝트를 마이그레이션하려면 `pages` 디렉토리의 페이지들을 `app` 디렉토리로 옮겨야 합니다. 그에 따라 라우팅 로직을 조정해야 할 수도 있습니다. Next.js는 이 과정을 돕기 위해 마이그레이션 가이드를 제공합니다.

파일 기반 라우팅의 핵심 개념

앱 라우터는 라우트가 처리되는 방식을 정의하는 몇 가지 특수 파일과 규칙을 도입합니다:

1. `app` 디렉토리

`app` 디렉토리는 애플리케이션 라우트의 루트입니다. 이 디렉토리 내의 모든 파일과 폴더는 라우트를 생성하는 데 사용됩니다. `app` 디렉토리 외부에 있는 것들(마이그레이션 중인 경우 `pages` 디렉토리 등)은 앱 라우터에 의해 무시됩니다.

2. `page.js` 파일

`page.js` (또는 `page.jsx`, `page.ts`, `page.tsx`) 파일은 앱 라우터의 가장 기본적인 부분입니다. 특정 라우트 세그먼트에 대해 렌더링될 UI 컴포넌트를 정의합니다. 직접 접근 가능한 라우트 세그먼트를 만들려면 **필수적인** 파일입니다.

예제:

다음과 같은 파일 구조가 있다고 가정해 봅시다:

app/
  about/
    page.js

사용자가 `/about`으로 이동하면 `app/about/page.js`에서 내보낸 컴포넌트가 렌더링됩니다.

// app/about/page.js
import React from 'react';

export default function AboutPage() {
  return (
    <div>
      <h1>회사 소개</h1>
      <p>저희 회사에 대해 더 알아보세요.</p>
    </div>
  );
}

3. `layout.js` 파일

`layout.js` (또는 `layout.jsx`, `layout.ts`, `layout.tsx`) 파일은 라우트 세그먼트 내의 여러 페이지에 걸쳐 공유되는 UI를 정의합니다. 레이아웃은 일관된 헤더, 푸터, 사이드바 및 여러 페이지에 존재해야 하는 기타 요소를 만드는 데 유용합니다.

예제:

`/about` 페이지와 가상의 `/about/team` 페이지 모두에 헤더를 추가하고 싶다고 가정해 봅시다. `app/about` 디렉토리에 `layout.js` 파일을 만들 수 있습니다:

// app/about/layout.js
import React from 'react';

export default function AboutLayout({ children }) {
  return (
    <div>
      <header>
        <h1>우리 회사에 대하여</h1>
      </header>
      <main>{children}</main>
    </div>
  );
}

`children` prop은 동일한 디렉토리나 중첩된 디렉토리의 `page.js` 파일에 의해 렌더링되는 UI로 대체됩니다.

4. `template.js` 파일

`template.js` 파일은 `layout.js`와 유사하지만, 각 자식 라우트에 대해 컴포넌트의 새 인스턴스를 생성합니다. 이는 자식 라우트 간 이동 시 컴포넌트 상태를 유지하거나 재렌더링을 방지하려는 시나리오에 유용합니다. 레이아웃과 달리 템플릿은 탐색 시 재렌더링됩니다. 템플릿을 사용하는 것은 탐색 시 요소를 애니메이션화하는 데 좋습니다.

예제:

// app/template.js
'use client'

import { useState } from 'react'

export default function Template({ children }) {
  const [count, setCount] = useState(0)

  return (
    <main>
      <p>템플릿: {count}</p>
      <button onClick={() => setCount(count + 1)}>템플릿 업데이트</button>
      {children}
    </main>
  )
}

5. `loading.js` 파일

`loading.js` (또는 `loading.jsx`, `loading.ts`, `loading.tsx`) 파일을 사용하면 라우트 세그먼트가 로딩되는 동안 표시될 로딩 UI를 만들 수 있습니다. 이는 데이터를 가져오거나 다른 비동기 작업을 수행할 때 더 나은 사용자 경험을 제공하는 데 유용합니다.

예제:

// app/about/loading.js
import React from 'react';

export default function Loading() {
  return <p>회사 정보를 로딩 중입니다...</p>;
}

사용자가 `/about`으로 이동하면 `page.js` 컴포넌트가 완전히 렌더링될 때까지 `Loading` 컴포넌트가 표시됩니다.

6. `error.js` 파일

`error.js` (또는 `error.jsx`, `error.ts`, `error.tsx`) 파일을 사용하면 라우트 세그먼트 내에서 오류가 발생했을 때 표시될 사용자 정의 오류 UI를 만들 수 있습니다. 이는 더 사용자 친화적인 오류 메시지를 제공하고 전체 애플리케이션이 충돌하는 것을 방지하는 데 유용합니다.

예제:

// app/about/error.js
'use client'

import React from 'react';

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>오류가 발생했습니다!</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>다시 시도</button>
    </div>
  );
}

`/about` 페이지를 렌더링하는 동안 오류가 발생하면 `Error` 컴포넌트가 표시됩니다. `error` prop에는 오류에 대한 정보가 포함되어 있으며, `reset` 함수를 통해 사용자는 페이지를 다시 로드하려고 시도할 수 있습니다.

7. 라우트 그룹

라우트 그룹 `(groupName)`을 사용하면 URL 구조에 영향을 주지 않고 라우트를 구성할 수 있습니다. 폴더 이름을 괄호로 묶어 생성합니다. 이는 레이아웃과 공유 컴포넌트를 구성하는 데 특히 유용합니다.

예제:

app/
  (marketing)/
    about/
      page.js
    contact/
      page.js
  (shop)/
    products/
      page.js

이 예제에서 `about`과 `contact` 페이지는 `marketing` 그룹 아래에 그룹화되고 `products` 페이지는 `shop` 그룹 아래에 있습니다. URL은 각각 `/about`, `/contact`, `/products`로 유지됩니다.

8. 동적 라우트

동적 라우트를 사용하면 가변 세그먼트가 있는 라우트를 생성할 수 있습니다. 이는 데이터베이스나 API에서 가져온 데이터를 기반으로 콘텐츠를 표시하는 데 유용합니다. 동적 라우트 세그먼트는 세그먼트 이름을 대괄호로 묶어 정의합니다(예: `[id]`).

예제:

ID를 기반으로 개별 블로그 게시물을 표시하는 라우트를 만들고 싶다고 가정해 봅시다. 다음과 같은 파일 구조를 만들 수 있습니다:

app/
  blog/
    [id]/
      page.js

`[id]` 세그먼트는 동적 세그먼트입니다. 사용자가 `/blog/123`이나 `/blog/456`과 같은 URL로 이동하면 `app/blog/[id]/page.js`에서 내보낸 컴포넌트가 렌더링됩니다. `id` 매개변수의 값은 컴포넌트의 `params` prop에서 사용할 수 있습니다.

// app/blog/[id]/page.js
import React from 'react';

export default async function BlogPost({ params }) {
  const { id } = params;

  // 주어진 ID로 블로그 게시물 데이터 가져오기
  const post = await fetchBlogPost(id);

  if (!post) {
    return <p>블로그 게시물을 찾을 수 없습니다.</p>;
  }

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

async function fetchBlogPost(id) {
  // 데이터베이스나 API에서 데이터 가져오기 시뮬레이션
  return new Promise((resolve) => {
    setTimeout(() => {
      const posts = {
        '123': { title: '나의 첫 블로그 게시물', content: '이것은 나의 첫 블로그 게시물의 내용입니다.' },
        '456': { title: '또 다른 블로그 게시물', content: '이것은 좀 더 흥미로운 내용입니다.' },
      };
      resolve(posts[id] || null);
    }, 500);
  });
}

라우트에서 여러 동적 세그먼트를 사용할 수도 있습니다. 예를 들어, `/blog/[category]/[id]`와 같은 라우트를 가질 수 있습니다.

9. 캐치올(Catch-all) 세그먼트

캐치올 세그먼트를 사용하면 임의의 수의 세그먼트와 일치하는 라우트를 만들 수 있습니다. 이는 URL 구조가 사용자에 의해 결정되는 CMS와 같은 시나리오에 유용합니다. 캐치올 세그먼트는 세그먼트 이름 앞에 점 세 개를 추가하여 정의합니다(예: `[...slug]`).

예제:

app/
  docs/
    [...slug]/
      page.js

`[...slug]` 세그먼트는 `/docs` 뒤의 모든 세그먼트와 일치합니다. 예를 들어, `/docs/getting-started`, `/docs/api/users`, `/docs/advanced/configuration`과 일치합니다. `slug` 매개변수의 값은 일치하는 세그먼트를 포함하는 배열이 됩니다.

// app/docs/[...slug]/page.js
import React from 'react';

export default function DocsPage({ params }) {
  const { slug } = params;

  return (
    <div>
      <h1>문서</h1>
      <p>슬러그: {slug ? slug.join('/') : '슬러그 없음'}</p>
    </div>
  );
}

선택적 캐치올 세그먼트는 세그먼트 이름을 이중 대괄호 `[[...slug]]`로 묶어 만들 수 있습니다. 이렇게 하면 라우트 세그먼트가 선택 사항이 됩니다. 예제:

app/
  blog/
    [[...slug]]/
      page.js

이 설정은 `/blog`와 `/blog/any/number/of/segments` 모두에서 page.js 컴포넌트를 렌더링합니다.

10. 병렬 라우트

병렬 라우트를 사용하면 동일한 레이아웃에 하나 이상의 페이지를 동시에 렌더링할 수 있습니다. 이는 대시보드와 같이 페이지의 다른 섹션을 독립적으로 로드할 수 있는 복잡한 레이아웃에 특히 유용합니다. 병렬 라우트는 `@` 기호 뒤에 슬롯 이름을 붙여 정의합니다(예: `@sidebar`, `@main`).

예제:

app/
  @sidebar/
    page.js  // 사이드바 콘텐츠
  @main/
    page.js  // 메인 섹션 콘텐츠
  default.js // 필수: 병렬 라우트의 기본 레이아웃 정의

`default.js` 파일은 병렬 라우트를 사용할 때 필요합니다. 다른 슬롯들이 어떻게 결합되어 최종 레이아웃을 만드는지 정의합니다.

// app/default.js
export default function RootLayout({ children: { sidebar, main } }) {
  return (
    <div style={{ display: 'flex' }}>
      <aside style={{ width: '200px', backgroundColor: '#f0f0f0' }}>
        {sidebar}
      </aside>
      <main style={{ flex: 1, padding: '20px' }}>
        {main}
      </main>
    </div>
  );
}

11. 인터셉팅 라우트

인터셉팅 라우트를 사용하면 현재 레이아웃 내에서 애플리케이션의 다른 부분에서 라우트를 로드할 수 있습니다. 이는 모달, 이미지 갤러리 및 기존 페이지 콘텐츠 위에 나타나야 하는 기타 UI 요소를 만드는 데 유용합니다. 인터셉팅 라우트는 `(..)` 구문을 사용하여 정의되며, 이는 인터셉트된 라우트를 찾기 위해 디렉토리 트리에서 얼마나 위로 올라갈지를 나타냅니다.

예제:

app/
  (.)photos/
    [id]/
      page.js  // 인터셉트된 라우트
  feed/
    page.js  // 사진 모달이 표시되는 페이지

이 예제에서 사용자가 `/feed` 페이지에서 사진을 클릭하면 `app/(.)photos/[id]/page.js` 라우트가 인터셉트되어 `/feed` 페이지 위에 모달로 표시됩니다. `(.)` 구문은 Next.js에게 한 단계 위(`app` 디렉토리)로 올라가 `photos/[id]` 라우트를 찾도록 지시합니다.

앱 라우터로 데이터 가져오기

앱 라우터는 서버 컴포넌트와 클라이언트 컴포넌트를 사용하여 데이터 가져오기를 기본적으로 지원합니다. 서버 컴포넌트는 서버에서 렌더링되고 클라이언트 컴포넌트는 클라이언트에서 렌더링됩니다. 이를 통해 각 컴포넌트의 요구 사항에 따라 최상의 접근 방식을 선택할 수 있습니다.

서버 컴포넌트

서버 컴포넌트는 앱 라우터의 기본값입니다. 별도의 API 라우트 없이 컴포넌트에서 직접 데이터를 가져올 수 있습니다. 이를 통해 성능을 개선하고 코드를 단순화할 수 있습니다.

예제:

// app/products/page.js
import React from 'react';

export default async function ProductsPage() {
  const products = await fetchProducts();

  return (
    <div>
      <h1>제품</h1>
      <ul>
        {products.map((product) => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </div>
  );
}

async function fetchProducts() {
  // 데이터베이스나 API에서 데이터 가져오기 시뮬레이션
  return new Promise((resolve) => {
    setTimeout(() => {
      const products = [
        { id: 1, name: '제품 A' },
        { id: 2, name: '제품 B' },
        { id: 3, name: '제품 C' },
      ];
      resolve(products);
    }, 500);
  });
}

이 예제에서는 `fetchProducts` 함수가 `ProductsPage` 컴포넌트 내에서 직접 호출됩니다. 컴포넌트는 서버에서 렌더링되고, HTML이 클라이언트로 전송되기 전에 데이터가 가져와집니다.

클라이언트 컴포넌트

클라이언트 컴포넌트는 클라이언트에서 렌더링되며 이벤트 리스너, 상태 및 브라우저 API와 같은 클라이언트 측 기능을 사용할 수 있습니다. 클라이언트 컴포넌트를 사용하려면 파일 상단에 `'use client'` 지시문을 추가해야 합니다.

예제:

// app/counter/page.js
'use client'

import React, { useState } from 'react';

export default function CounterPage() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>카운터</h1>
      <p>횟수: {count}</p>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </div>
  );
}

이 예제에서 `CounterPage` 컴포넌트는 `useState` 훅을 사용하기 때문에 클라이언트 컴포넌트입니다. `'use client'` 지시문은 Next.js에 이 컴포넌트를 클라이언트에서 렌더링하도록 지시합니다.

고급 라우팅 기술

앱 라우터는 복잡하고 정교한 애플리케이션을 만드는 데 사용할 수 있는 몇 가지 고급 라우팅 기술을 제공합니다.

1. 라우트 핸들러

라우트 핸들러를 사용하면 `app` 디렉토리 내에 API 엔드포인트를 생성할 수 있습니다. 이렇게 하면 별도의 `pages/api` 디렉토리가 필요하지 않습니다. 라우트 핸들러는 `route.js` (또는 `route.ts`)라는 이름의 파일에 정의되며, 다른 HTTP 메서드(예: `GET`, `POST`, `PUT`, `DELETE`)를 처리하는 함수를 내보냅니다.

예제:

// app/api/users/route.js
import { NextResponse } from 'next/server'

export async function GET(request) {
  // 데이터베이스에서 사용자 가져오기 시뮬레이션
  const users = [
    { id: 1, name: '홍길동' },
    { id: 2, name: '김영희' },
  ];

  return NextResponse.json(users);
}

export async function POST(request) {
  const body = await request.json()
  console.log('수신된 데이터:', body)
  return NextResponse.json({ message: '사용자가 생성되었습니다' }, { status: 201 })
}

이 예제는 `/api/users`에서 `GET` 및 `POST` 요청을 모두 처리하는 라우트 핸들러를 정의합니다. `GET` 함수는 사용자 목록을 반환하고, `POST` 함수는 새 사용자를 생성합니다.

2. 다중 레이아웃을 가진 라우트 그룹

라우트 그룹을 레이아웃과 결합하여 애플리케이션의 다른 섹션에 대해 다른 레이아웃을 만들 수 있습니다. 이는 사이트의 다른 부분에 대해 다른 헤더나 사이드바를 갖고 싶을 때 유용합니다.

예제:

app/
  (marketing)/
    layout.js  // 마케팅 레이아웃
    about/
      page.js
    contact/
      page.js
  (admin)/
    layout.js  // 관리자 레이아웃
    dashboard/
      page.js

이 예제에서 `about`과 `contact` 페이지는 `marketing` 레이아웃을 사용하고, `dashboard` 페이지는 `admin` 레이아웃을 사용합니다.

3. 미들웨어

미들웨어를 사용하면 요청이 애플리케이션에 의해 처리되기 전에 코드를 실행할 수 있습니다. 이는 인증, 권한 부여, 로깅 및 사용자의 위치나 장치에 따라 사용자를 리디렉션하는 등의 작업에 유용합니다.

미들웨어는 프로젝트의 루트에 있는 `middleware.js` (또는 `middleware.ts`) 파일에 정의됩니다.

예제:

// middleware.js
import { NextResponse } from 'next/server'

export function middleware(request) {
  // 사용자가 인증되었는지 확인
  const isAuthenticated = false; // 실제 인증 로직으로 교체하세요

  if (!isAuthenticated && request.nextUrl.pathname.startsWith('/admin')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

// 자세한 내용은 아래 "경로 매칭"을 참조하세요
export const config = {
  matcher: '/admin/:path*',
}

이 예제는 사용자가 `/admin` 아래의 모든 라우트에 접근하기 전에 인증되었는지 확인하는 미들웨어를 정의합니다. 사용자가 인증되지 않은 경우 `/login` 페이지로 리디렉션됩니다.

파일 기반 라우팅을 위한 모범 사례

앱 라우터의 파일 기반 라우팅 시스템을 최대한 활용하려면 다음 모범 사례를 고려하세요:

Next.js 앱 라우터를 이용한 국제화 예제

Next.js 앱 라우터는 파일 기반 라우팅을 통해 국제화(i18n)를 단순화합니다. 다음은 i18n을 효과적으로 구현하는 방법입니다:

1. 하위 경로 라우팅

하위 경로를 사용하여 로케일(locale)에 따라 라우트를 구성합니다. 예를 들어:

app/
  [locale]/
    page.tsx         // 해당 로케일의 홈 페이지
    about/
      page.tsx     // 해당 로케일의 About 페이지
// app/[locale]/page.tsx
import { getTranslations } from './dictionaries';

export default async function HomePage({ params: { locale } }) {
  const t = await getTranslations(locale);
  return (<h1>{t.home.title}</h1>);
}

// dictionaries.js
const dictionaries = {
  en: () => import('./dictionaries/en.json').then((module) => module.default),
  es: () => import('./dictionaries/es.json').then((module) => module.default),
};

export const getTranslations = async (locale) => {
  try {
    return dictionaries[locale]() ?? dictionaries.en();
  } catch (error) {
    console.error(`로케일 ${locale}의 번역을 로드하는 데 실패했습니다`, error);
    return dictionaries.en();
  }
};

이 설정에서 `[locale]` 동적 라우트 세그먼트는 다른 로케일(예: `/en`, `/es`)을 처리합니다. 번역은 로케일에 따라 동적으로 로드됩니다.

2. 도메인 라우팅

더 고급 접근 방식으로, 각 로케일에 대해 다른 도메인이나 하위 도메인을 사용할 수 있습니다. 이는 종종 호스팅 제공업체와의 추가 구성이 필요합니다.

3. 로케일 감지를 위한 미들웨어

미들웨어를 사용하여 사용자의 선호 로케일을 자동으로 감지하고 그에 따라 리디렉션합니다.

// middleware.js
import { NextResponse } from 'next/server';
import { match } from '@formatjs/intl-localematcher';
import Negotiator from 'negotiator';

let locales = ['en', 'es', 'fr'];

function getLocale(request) {
  const negotiatorHeaders = {};
  request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));
  let languages = new Negotiator({ headers: negotiatorHeaders }).languages();

  try {
      return match(languages, locales, 'en'); // "en"을 기본 로케일로 사용
  } catch (error) {
      console.error("로케일 매칭 오류:", error);
      return 'en'; // 매칭 실패 시 영어로 대체
  }
}

export function middleware(request) {
  const pathname = request.nextUrl.pathname;
  const pathnameIsMissingLocale = locales.every(
    (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
  );

  if (pathnameIsMissingLocale) {
    const locale = getLocale(request);

    return NextResponse.redirect(
      new URL(
        `/${locale}${pathname.startsWith('/') ? '' : '/'}${pathname}`,
        request.url
      )
    );
  }
}

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

이 미들웨어는 요청된 경로에 로케일 접두사가 있는지 확인합니다. 없는 경우 `Accept-Language` 헤더를 사용하여 사용자의 선호 로케일을 감지하고 적절한 로케일별 경로로 리디렉션합니다. `@formatjs/intl-localematcher` 및 `negotiator`와 같은 라이브러리는 로케일 협상을 처리하는 데 사용됩니다.

Next.js 앱 라우터와 글로벌 접근성

전 세계적으로 접근 가능한 웹 애플리케이션을 만들려면 접근성(a11y) 원칙을 신중하게 고려해야 합니다. Next.js 앱 라우터는 접근성 있는 경험을 구축하기 위한 견고한 기반을 제공하지만, 능력에 관계없이 모든 사람이 애플리케이션을 사용할 수 있도록 모범 사례를 구현하는 것이 중요합니다.

주요 접근성 고려 사항

  1. 시맨틱 HTML: 콘텐츠를 구조화하기 위해 시맨틱 HTML 요소(예: `<article>`, `<nav>`, `<aside>`, `<main>`)를 사용하세요. 이는 보조 기술에 의미를 제공하고 사용자가 사이트를 더 쉽게 탐색할 수 있도록 돕습니다.
  2. ARIA 속성: ARIA(Accessible Rich Internet Applications) 속성을 사용하여 사용자 정의 컴포넌트 및 위젯의 접근성을 향상시키세요. ARIA 속성은 요소의 역할, 상태 및 속성에 대한 추가 정보를 보조 기술에 제공합니다.
  3. 키보드 탐색: 모든 상호작용 요소가 키보드를 통해 접근 가능한지 확인하세요. 사용자는 `Tab` 키를 사용하여 애플리케이션을 탐색하고 `Enter` 또는 `Space` 키를 사용하여 요소와 상호작용할 수 있어야 합니다.
  4. 색상 대비: 시각 장애가 있는 사용자의 가독성을 보장하기 위해 텍스트와 배경 간에 충분한 색상 대비를 사용하세요. 웹 콘텐츠 접근성 가이드라인(WCAG)은 일반 텍스트의 경우 최소 4.5:1, 큰 텍스트의 경우 3:1의 대비율을 권장합니다.
  5. 이미지 대체 텍스트: 모든 이미지에 대해 설명적인 대체 텍스트(alt text)를 제공하세요. 대체 텍스트는 스크린 리더가 읽을 수 있는 이미지의 텍스트 대안을 제공합니다.
  6. 폼 레이블: `<label>` 요소를 사용하여 폼 레이블을 해당 입력 필드와 연결하세요. 이렇게 하면 사용자가 각 필드에 어떤 정보가 필요한지 명확하게 알 수 있습니다.
  7. 스크린 리더 테스트: 스크린 리더로 애플리케이션을 테스트하여 시각 장애가 있는 사용자에게 접근 가능한지 확인하세요. 인기 있는 스크린 리더로는 NVDA, JAWS, VoiceOver가 있습니다.

Next.js 앱 라우터에서 접근성 구현하기

  1. Next.js Link 컴포넌트 사용: 탐색을 위해 `<Link>` 컴포넌트를 사용하세요. 프리페칭 및 포커스 관리와 같은 내장된 접근성 기능을 제공합니다.
  2. 포커스 관리: 페이지 간 이동이나 모달을 열 때 포커스가 적절하게 관리되는지 확인하세요. 포커스는 새 페이지나 모달의 가장 논리적인 요소에 설정되어야 합니다.
  3. 접근성 있는 사용자 정의 컴포넌트: 사용자 정의 컴포넌트를 만들 때 위에서 설명한 원칙을 따라 접근성을 보장하세요. 시맨틱 HTML, ARIA 속성 및 키보드 탐색을 사용하여 모든 사람이 컴포넌트를 사용할 수 있도록 만드세요.
  4. 린팅 및 테스트: ESLint와 같은 린팅 도구와 접근성 플러그인을 사용하여 코드에서 잠재적인 접근성 문제를 식별하세요. 또한 자동화된 테스트 도구를 사용하여 애플리케이션의 접근성 위반을 테스트하세요.

결론

Next.js 앱 라우터의 파일 기반 라우팅 시스템은 애플리케이션을 구조화하고 탐색하는 강력하고 직관적인 방법을 제공합니다. 이 가이드에서 설명한 핵심 개념과 모범 사례를 이해함으로써 견고하고 확장 가능하며 유지보수 가능한 Next.js 애플리케이션을 구축할 수 있습니다. 앱 라우터의 다양한 기능을 실험해보고 개발 워크플로를 단순화하고 사용자 경험을 개선하는 방법을 알아보세요.