(번역)Next.js 앱 라우터 마스터링: 애플리케이션 구조화를 위한 모범 사례

1

nextjs

목록 보기
1/2
post-thumbnail

원문: https://thiraphat-ps-dev.medium.com/mastering-next-js-app-router-best-practices-for-structuring-your-application-3f8cf0c76580

Next.js는 선도적인 React 프레임워크 중 하나로서 입지를 굳건히 했으며, 개발자에게 성능, 유연성, 사용 편의성을 매끄럽게 결합했다. App Router가 출시되면서 Next.js는 또 다른 도약을 이루어 더욱 직관적이고 강력한 라우팅 메커니즘을 제공했다. 개인 블로그를 구축하든 대규모 엔터프라이즈 애플리케이션을 구축하든 Next.js 프로젝트를 효과적으로 구조화하는 것은 유지 관리, 확장성, 개발자 경험에 매우 중요하다.

이 문서에서는 App Router를 사용하여 Next.js 애플리케이션을 구조화하는 모범 사례를 살펴보고자 한다. 이를 통해 프로젝트가 체계적이고 효율적이며 탐색하기 쉬운 상태를 유지할 수 있다.

목차

  1. Next.js 앱 라우터 이해
  2. 프로젝트 구조 개요
  3. 페이지 및 경로 구성
  4. 구성 요소를 효과적으로 관리하기
  5. 스타일링 전략
  6. 상태 관리 모범 사례
  7. 성능 최적화
  8. 테스트 및 품질 보증
  9. 배포 고려 사항
  10. 결론

Next.js 앱 라우터 이해

구조를 먼저 보기 전에 App Router가 무엇을 제공하는지 파악하는 것이 중요하다. Next.js의 App Router는 다음과 같은 기능을 도입하여 라우팅 기능을 향상시킨다.

  • 중첩 라우팅 : 중첩된 레이아웃으로 복잡한 경로 계층을 지원
  • 동적 경로 : 동적 URL 생성을 간소화
  • 서버 구성 요소 : 더 나은 성능을 위해 서버에서 구성 요소를 렌더링
  • 향상된 데이터 가져오기 : 클라이언트 측 부하를 줄여서 데이터를 가져오는 더 나은 방법을 제공

이러한 기능을 이해하는 것은 프로젝트 구조에서 App Router를 효과적으로 활용하는 데 필수적이다.

프로젝트 구조 개요

잘 구성된 프로젝트 구조는 가독성, 유지 관리성, 확장성을 향상시킨다. App Router를 사용하는 Next.js 애플리케이션에 권장되는 디렉토리 레이아웃은 다음과 같다.

my-nextjs-app/
├── app/
│   ├── layout.js
│   ├── page.js
│   ├── dashboard/
│   │   ├── layout.js
│   │   ├── page.js
│   │   └── settings/
│   │       └── page.js
│   └── blog/
│       ├── layout.js
│       ├── page.js
│       └── [slug]/
│           └── page.js
├── components/
│   ├── Header.js
│   ├── Footer.js
│   └── ... 
├── styles/
│   ├── globals.css
│   └── ...
├── public/
│   ├── images/
│   └── ... 
├── lib/
│   ├── api.js
│   └── ... 
├── hooks/
│   ├── useAuth.js
│   └── ... 
├── tests/
│   ├── components/
│   └── ... 
├── package.json
├── next.config.js
└── ...

주요 디렉토리 및 파일

  • app/ : App Router를 활용하는 모든 경로와 레이아웃을 포함
  • components/ : 재사용 가능한 UI 구성요소
  • styles/ : 글로벌 및 구성 요소별 스타일
  • public/ : 이미지, 글꼴 등과 같은 정적 자산
  • lib/ : 유틸리티 함수 및 라이브러리
  • hooks/ : 사용자 정의 React hooks
  • tests/ : 애플리케이션의 다양한 부분에 대한 테스트 모음

페이지 및 경로 구성

App Router를 통해 Next.js는 디렉토리 구조가 URL 구조를 반영하는 파일 기반 라우팅 시스템을 장려한다. 페이지와 경로를 효과적으로 구성하는 방법은 다음과 같다.

중첩된 경로 및 레이아웃

중첩 라우팅을 활용하여 계층적 레이아웃을 만든다. 예를 들어, 대시보드 섹션에는 주 애플리케이션 레이아웃과 별도로 자체 레이아웃이 있을 수 있다.

app/
├── layout.js           // Main application layout
├── page.js             // Home page
├── dashboard/
│   ├── layout.js       // Dashboard-specific layout
│   ├── page.js         // Dashboard home
│   └── settings/
│       └── page.js     // Dashboard settings

동적 경로

대괄호 표기법으로 폴더를 만들어 동적 콘텐츠를 처리한다. 예를 들어, 블로그 게시물은 /blog/[slug] 을 통해 액세스할 수 있다.

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

page.js 파일 내부에는:

export default function BlogPost({ params }) {
  const { slug } = params;
  // Fetch and render blog post based on slug
}

Catch-All Routes(캐치올 경로)

여러 구간을 캡처해야 하는 경로의 경우 [...param]구문을 사용한다.

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

이 설정은 다음과 같은 URL을 처리할 수 있다. /docs/intro/getting-started.

구성 요소를 효과적으로 관리하기

구성요소를 구성하는 것은 재사용성과 유지관리에 필수적이다.

Atomic Design Principles

원자 설계 원칙을 채택하여 구성 요소를 분류

  • Atoms(원자) : 버튼, 입력과 같은 기본적인 구성 요소
  • Molecules(분자) : 기능적 단위를 형성하는 원자의 조합
  • Organisms(유기체) : 분자와 원자로 구성된 복잡한 UI 섹션
  • Templates(템플릿) : 페이지 수준 구조.
  • Pages(페이지) : 템플릿과 데이터를 결합한 특정 인스턴스

디렉토리 구조:

components/
├── atoms/
│   ├── Button.js
│   └── Input.js
├── molecules/
│   ├── FormGroup.js
│   └── Card.js
├── organisms/
│   ├── Header.js
│   └── Footer.js
└── ...

구성 요소 명명 및 파일 구조

예를 들어, Button.js구성 요소와 Button.module.css해당 스타일에 대해 명확하고 일관된 명명 규칙을 사용한다.

// components/atoms/Button.js
import styles from './Button.module.css';

export default function Button({ children, onClick }) {
  return (
    <button className={styles.button} onClick={onClick}>
      {children}
    </button>
  );
}

스타일링 전략

올바른 스타일링 방식을 선택하면 개발 워크플로와 애플리케이션 성능에 영향을 미칠 수 있다.

CSS 모듈

Next.js는 기본적으로 CSS 모듈을 지원하여 범위가 지정되고 모듈화된 CSS를 사용할 수 있다.

components/
└── atoms/
    └── Button.module.css

스타일이 적용된 구성 요소 또는 CSS-in-JS

동적인 스타일과 테마를 적용하려면 Styled Components나 Emotion과 같은 라이브러리를 고려해야 한다.

// components/atoms/Button.js
import styled from 'styled-components';

const StyledButton = styled.button`
  background-color: #0070f3;
  color: white;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
`;
export default function Button({ children, onClick }) {
  return <StyledButton onClick={onClick}>{children}</StyledButton>;
}

테일윈드 CSS

Tailwind는 HTML을 벗어나지 않고도 빠르게 스타일을 지정할 수 있는 유틸리티 중심의 CSS 클래스를 제공한다.

// components/atoms/Button.js
export default function Button({ children, onClick }) {
  return (
    <button
      className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
      onClick={onClick}
    >
      {children}
    </button>
  );
}

글로벌 스타일

globals.css기본 스타일과 글로벌 구성에 이 파일을 사용하면 된다.

/* styles/globals.css */
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
    Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

루트 레이아웃으로 가져오는 경우:

// app/layout.js
import '../styles/globals.css';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

상태 관리 모범 사례

효과적인 상태 관리를 통해 애플리케이션이 예측 가능하고 디버깅하기 쉬운 상태를 유지할 수 있다.

React Hooks를 사용한 로컬 상태

컴포넌트 수준 상태를 위해 React의 기본 제공 Hooks인 useStateuseReducer를 사용한다.

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Context API를 사용한 글로벌 상태

여러 구성 요소에서 상태를 공유하려면 React의 Context API를 활용하면 된다.

// context/AuthContext.js
import { createContext, useState } from 'react';

export const AuthContext = createContext();
export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  
  return (
    <AuthContext.Provider value={{ user, setUser }}>
      {children}
    </AuthContext.Provider>
  );
}

provider와 함께 애플리케이션을 감싸면:

// app/layout.js
import { AuthProvider } from '../context/AuthContext';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <AuthProvider>
          {children}
        </AuthProvider>
      </body>
    </html>
  );
}

전역 관리 라이브러리

복잡한 상태 요구 사항에는 Redux, Zustand, Recoil과 같은 라이브러리를 고려할 필요가 있다.

// Using Zustand
import create from 'zustand';

const useStore = create(set => ({
  user: null,
  setUser: (user) => set({ user }),
}));
export default useStore;

성능 최적화

성능 최적화는 원활한 사용자 경험과 더 나은 SEO 순위를 보장한다.

코드 분할 및 지연 로딩

Next.js는 자동으로 코드를 분할하지만, 동적 가져오기를 사용하여 추가로 최적화 할 수 있다.

import dynamic from 'next/dynamic';

const HeavyComponent = dynamic(() => import('../components/HeavyComponent'), {
  loading: () => <p>Loading...</p>,
});

이미지 최적화

최적화된 이미지 로딩을 위해 Next.js의 Image컴포넌트를 사용해야 한다.

import Image from 'next/image';

export default function Profile() {
  return (
    <Image
      src="/images/profile.jpg"
      alt="Profile Picture"
      width={200}
      height={200}
    />
  );
}

캐싱 및 CDN

캐싱 전략과 콘텐츠 전송 네트워크(CDN)를 활용하여 정적 자산을 효율적으로 제공한다.

서버 사이드 렌더링(SSR) 및 정적 사이트 생성(SSG)

콘텐츠의 동적 특성에 따라 SSR과 SSG 중에서 선택하여 성능과 최신성 사이의 균형을 유지해라.

// pages/index.js
export async function getStaticProps() {
  const data = await fetchData();
  return {
    props: { data },
    revalidate: 60, // Revalidate every 60 seconds
  };
}

테스트 및 품질 보증

테스트를 구현하면 애플리케이션이 강력하게 유지되고 회귀 현상이 발생하지 않게 된다.

단위 테스트

단위 테스트에는 Jest와 React Testing Library와 같은 프레임워크를 사용해라.

// tests/components/Button.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import Button from '../../components/atoms/Button';

test('renders button and handles click', () => {
  const handleClick = jest.fn();
  render(<Button onClick={handleClick}>Click Me</Button>);
  
  const button = screen.getByText('Click Me');
  fireEvent.click(button);
  
  expect(handleClick).toHaveBeenCalledTimes(1);
});

통합 테스트

애플리케이션의 다양한 부분이 어떻게 함께 작동하는지 테스트한다.javascript

// tests/pages/HomePage.test.js
import { render, screen } from '@testing-library/react';
import HomePage from '../../app/page';

test('renders home page with header and footer', () => {
  render(<HomePage />);
  expect(screen.getByText('Welcome')).toBeInTheDocument();
  expect(screen.getByText('Footer')).toBeInTheDocument();
});

엔드투엔드(E2E) 테스트

Cypress나 Playwright와 같은 도구를 사용하여 E2E 테스트를 실시하여 사용자 상호작용을 시뮬레이션한다.

// cypress/integration/home.spec.js
describe('Home Page', () => {
  it('loads successfully', () => {
    cy.visit('/');
    cy.contains('Welcome').should('be.visible');
  });
});

배포 고려 사항

Next.js 애플리케이션을 효율적으로 배포하면 다운타임을 최소화하고 최적의 성능을 보장할 수 있다.

vercel

Next.js를 개발한 팀이 만든 Vercel은 최적화된 성능으로 원활한 배포를 제공한다.

다른 플랫폼

Netlify, AWS 또는 DigitalOcean과 같은 플랫폼에도 배포할 수 있다. SSR 및 API 경로가 올바르게 구성되었는지 확인햐러,`

환경 변수

환경 변수를 사용하여 민감한 데이터를 관리합니다.

# .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
SECRET_KEY=your-secret-key

당신의 application을 액세스하면:

const apiUrl = process.env.NEXT_PUBLIC_API_URL;

지속적인 통합 및 배포(CI/CD)

GitHub Actions, GitLab CI 또는 기타 도구를 사용하여 CI/CD 파이프라인을 구현하여 테스트 및 배포를 자동화한다.

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [ main ]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install Dependencies
        run: npm install
      - name: Run Tests
        run: npm test
      - name: Build
        run: npm run build

결론

App Router를 사용하여 Next.js 애플리케이션을 효과적으로 구조화하는 것은 확장 가능하고 유지 관리가 가능하며 고성능 웹 애플리케이션을 만드는 데 매우 중요하다. 이 가이드에 설명된 모범 사례를 준수하면(프로젝트 구조 구성, 구성 요소 관리, 성능 최적화, 강력한 테스트 구현) Next.js의 모든 잠재력을 활용하고 뛰어난 사용자 경험을 제공할 수 있다.

이러한 관행을 도입하여 개발 워크플로를 간소화하고, 팀 협업을 용이하게 하고, 끊임없이 변화하는 웹 환경에서 애플리케이션이 시간의 시험을 견뎌낼 수 있도록 하길 바란다.

profile
`나는 ${job} 개발자`

0개의 댓글