25.07.01 - se5ri/React GitHub Wiki

ch10

4.5 ํŽ˜์ด์ง€ ์ด๋™

4.5.1 Link ์ปดํฌ๋„ŒํŠธ

  • next/link ํŒจํ‚ค์ง€์— ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ

    • import Link from "next/link"
  • Next.js์˜ App ๋ผ์šฐํ„ฐ์˜ ๋ผ์šฐํŒ… ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ ์ปดํฌ๋„ŒํŠธ

  • a ํƒœ๊ทธ ๋Œ€์‹  ์‚ฌ์šฉ

    • Link ์ปดํฌ๋„ŒํŠธ๋Š” ๋ Œ๋”๋ง ๋˜๋ฉด a ํƒœ๊ทธ๋กœ ๋ฐ”๋€œ

app/layout.tsx ์ˆ˜์ •

import Link from "next/link";
...
<li><Link href="/" className={`hover:underline`}>Home</Link></li>
<li><Link href="/about" className={`hover:underline`}>About</Link></li>
<li><Link href="/posts" className={`hover:underline`}>๊ฒŒ์‹œํŒ</Link></li>
<li><Link href="/user/login" className={`hover:underline`}>๋กœ๊ทธ์ธ</Link></li>
<li><Link href="/user/signup" className={`hover:underline`}>ํšŒ์›๊ฐ€์ž…</Link></li>
...

app/posts/layout.tsx ์ˆ˜์ •

import Link from "next/link";
...
<li><Link href="/posts" className="block hover:bg-gray-700 p-2 rounded">๋ชฉ๋ก ์กฐํšŒ</Link></li>
<li><Link href="/posts/new" className="block hover:bg-gray-700 p-2 rounded">๊ธ€์“ฐ๊ธฐ</Link></li>
...

ํ™œ์„ฑ ๋งํฌ ์ฒดํฌ

  • usePathname() ํ›…์„ ์ด์šฉํ•ด์„œ url ํ™•์ธ ํ›„ href์™€ ๋น„๊ต

  • app/globals.css ์ž‘์„ฑ

    ...
    /* Tailwind CSS์˜ ์ปค์Šคํ…€ ์ปดํฌ๋„ŒํŠธ ํด๋ž˜์Šค ์ •์˜ */
    @layer components {
      .cs-active {
        @apply text-orange-500;
      }
    }
  • app/layout.tsx ์ˆ˜์ •

    'use client';
    
    import './globals.css';
    import Link from 'next/link';
    import { usePathname } from 'next/navigation';
    
    export default function RootLayout({
      children,
    }: {
      children: React.ReactNode
    }) {
    
      const pathname = usePathname();
      console.log(pathname);
      const isActive = (path: string) => pathname === path ? 'cs-active' : '';
    
      return (
        <html lang="ko">
          <body className="flex flex-col h-screen">
            <header className="bg-blue-500 text-white p-4">
              <nav>
                <ul className="flex space-x-4">
                  <li><Link href="/" className={`hover:underline ${isActive('/')}`}>Home</Link></li>
                  <li><Link href="/about" className={`hover:underline ${isActive('/about')}`}>About</Link></li>
                  <li><Link href="/posts" className={`hover:underline ${isActive('/posts')}`}>๊ฒŒ์‹œํŒ</Link></li>
                  <li><Link href="/user/login" className={`hover:underline ${isActive('/user/login')}`}>๋กœ๊ทธ์ธ</Link></li>
                  <li><Link href="/user/signup" className={`hover:underline ${isActive('/user/signup')}`}>ํšŒ์›๊ฐ€์ž…</Link></li>
                </ul>
              </nav>
            </header>
    
            { children }
            
          </body>
        </html>
      );
    }

4.5.2 useRouter ํ›…

  • ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ํŽ˜์ด์ง€ ์ด๋™ ๊ฐ€๋Šฅ

  • ํŽ˜์ด์ง€ ์ด๋™ ์‹œ SSR, SSG, ๋ฐ์ดํ„ฐ fetching, ์ „ํ™˜ ํšจ๊ณผ ๋“ฑ Next.js์˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ง์ ‘ window.location์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๊ถŒ์žฅ๋จ

  • ๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด Link ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ ๊ถŒ์žฅ

  • 'use client' ์ง€์‹œ์–ด๊ฐ€ ์žˆ๋Š” ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

    'use client'
    import { useRouter } from 'next/navigation';

์ฃผ์š” ๋ฉ”์„œ๋“œ

  • router.push(url): ์ง€์ •ํ•œ ๊ฒฝ๋กœ๋กœ ์ด๋™(ํŽ˜์ด์ง€ ์ถ”๊ฐ€)
  • router.replace(url): ์ง€์ •ํ•œ ๊ฒฝ๋กœ๋กœ ์ด๋™(ํŽ˜์ด์ง€ ๊ต์ฒด)
  • router.back(): ์ด์ „ ํŽ˜์ด์ง€๋กœ ์ด๋™
  • router.forward(): ๋‹ค์Œ ํŽ˜์ด์ง€๋กœ ์ด๋™
  • router.refresh(): ํ˜„์žฌ ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ

4.5.3 redirect

  • ์„œ๋ฒ„์ธก์—์„œ ํŽ˜์ด์ง€ ์ด๋™(๋ฆฌ๋””๋ ‰์…˜) ์‹œ ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜

    • ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ, ์„œ๋ฒ„ ํ•จ์ˆ˜, ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ์—์„œ ์‚ฌ์šฉ
  • ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง ์ค‘์—๋Š” ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ๋Š” ์‚ฌ์šฉ ๋ถˆ๊ฐ€

    • ํด๋ผ์ด์–ธํŠธ์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ๋Š” useRouter์˜ push/replace ์‚ฌ์šฉ
  • ๊ธฐ๋ณธ์ ์œผ๋กœ 307 ์ƒํƒœ ์ฝ”๋“œ๋กœ ์‘๋‹ต

    • 307 ์‘๋‹ต ์ƒํƒœ์ฝ”๋“œ: Temporary Redirect, ์›๋ž˜ ์š”์ฒญ ๋ฐฉ์‹๊ณผ ๋ณธ๋ฌธ์œผ๋กœ ์ƒˆ๋กœ์šด ํŽ˜์ด์ง€ ์š”์ฒญ, ๋‹ค์Œ๋ฒˆ ์š”์ฒญ์—๋„ ์ด์ „ URL ์‚ฌ์šฉ
  • ์„œ๋ฒ„ ํ•จ์ˆ˜์ผ ๊ฒฝ์šฐ(POST ์š”์ฒญ์˜ ์„ฑ๊ณต ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•  ๋•Œ) 303 ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ๋กœ ์‘๋‹ต

    • 303 ์‘๋‹ต ์ƒํƒœ์ฝ”๋“œ: See Other, ์ƒˆ๋กœ์šด ํŽ˜์ด์ง€๋กœ GET ์š”์ฒญ
    import { redirect } from 'next/navigation';

์˜ˆ์‹œ

'use server';
import { redirect } from 'next/navigation';

export async function createPost(formData) {
  // ๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก ๋กœ์ง...
  redirect('/posts'); // ๋“ฑ๋ก ํ›„ ๋ชฉ๋ก์œผ๋กœ ์ด๋™
}

4.5.4 permanentRedirect

  • ์‘๋‹ต ์ƒํƒœ์ฝ”๋“œ๊ฐ€ 308์ธ ์ ๋งŒ ๋‹ค๋ฅด๊ณ  redirect์™€ ๋™์ผ
    • 308 ์‘๋‹ด ์ƒํƒœ ์ฝ”๋“œ: Permanent Redirect, ์›๋ž˜ ์š”์ฒญ ๋ฐฉ์‹๊ณผ ๋ณธ๋ฌธ์œผ๋กœ ์ƒˆ๋กœ์šด ํŽ˜์ด์ง€ ์š”์ฒญ, ๋‹ค์Œ๋ถ€ํ„ฐ๋Š” ์ƒˆ๋กœ์šด URL ์‚ฌ์šฉ

4.5.5 history API

  • ๋ธŒ๋ผ์šฐ์ €์˜ history API ์‚ฌ์šฉ
    • window.history.pushState
    • window.history.replaceState
  • usePathname(), useSearchParams() ํ›…์œผ๋กœ URL๊ณผ ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”์ถœํ•ด์„œ low-level๋กœ URL ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ
  • useRouter() ํ›…์„ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ํŽ˜์ด์ง€ ์ „ํ™˜ ์‹œ SSR, SSG, ๋ฐ์ดํ„ฐ fetching, ํŽ˜์ด์ง€ ์ „ํ™˜ ํšจ๊ณผ ๋“ฑ Next.js ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ useRouter() ๊ถŒ์žฅ

4.5.6 next.config.ts์˜ redirects

  • ์„ ์–ธ์  redirect

    import type { NextConfig } from "next";
    
    const nextConfig: NextConfig = {
      /* config options here */
      async redirects() {
        return [
          {
            source: '/home',
            destination: '/',
            permanent: true,
          },
          {
            source: '/community/:slug',
            destination: '/posts/:slug',
            permanent: true,
          },
        ]
      },
    };
    
    export default nextConfig;

4.5.7 NextResponse.redirect

  • ๋ฏธ๋“ค์›จ์–ด์—์„œ ์‚ฌ์šฉ

  • ์‚ฌ์šฉ์‚ฌ๋ก€: ๋กœ๊ทธ์ธ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๋ฅผ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™

    import { NextResponse } from 'next/server';
    import { authenticate } from 'auth-provider';
    
    export function middleware(request) {
      const isAuthenticated = authenticate(request);
    
      // ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋ผ๋ฉด ์›๋ž˜์˜ ์š”์ฒญ์ž‘์—… ์ง„ํ–‰
      if (isAuthenticated) {
        return NextResponse.next();
      }
    
      // ์ธ์ฆ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๋ผ๋ฉด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™
      return NextResponse.redirect(new URL('/login', request.url));
    }
    
    export const config = {
      matcher: '/posts/new',
    }

4.6 ๋™์  ๋ผ์šฐํŠธ

  • ๊ณ ์ •๋œ URL์ด ์•„๋‹Œ ๋ฐ”๋€”์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ๋ผ์šฐํŒ…์„ ์ •์˜ํ•  ๋•Œ ํด๋”๋ช…์„ ๋Œ€๊ด„ํ˜ธ๋กœ ๋ฌถ์–ด์„œ ์ƒ์„ฑ
    • posts/1, posts/2 -> posts/[id]
  • ์‹ค์ œ ์š”์ฒญํ•œ URL์˜ ๋™์  ๋ผ์šฐํŠธ ๊ฐ’์€ layout, page, route, generateMetadata ํ•จ์ˆ˜์— params prop์œผ๋กœ ์ „๋‹ฌ๋จ

4.6.1 ์š”์ฒญํ•œ URL์ด /posts/3์ผ ๊ฒฝ์šฐ 3์„ ๊บผ๋‚ด๋Š” ๋ฐฉ๋ฒ•

  • app/posts/1 -> app/posts/[id]๋กœ ์ˆ˜์ •

  • app/posts/[id]/page.tsx์— ์ถ”๊ฐ€

    export default async function InfoPage({ params }: { params: { id: string } }) {
      const { id } = await params; // Next.js 15 ๋ถ€ํ„ฐ params๋Š” ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ํ•„์š”
      await new Promise(resolve => setTimeout(resolve, 1000*2));
      return (
        <h1>์ƒ์„ธ ์กฐํšŒ - { id }๋ฒˆ ๊ฒŒ์‹œ๋ฌผ</h1>
      );
    }
  • app/posts/[id]/page.tsx ํŒŒ์ผ์ด ์žˆ์„๋•Œ ๋งค์นญ๋˜๋Š” URL๊ณผ params ๊ฐ’

    • /posts/1 -> { id: '1' }
    • /posts/2 -> { id: '2' }
    • /posts/3 -> { id: '3' }
    • ...
  • ๋™์  ๋ผ์šฐํŠธ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํŠน์ • ๊ฒŒ์‹œ๊ธ€์— ๋‹ฌ๋ฆฐ ์ข‹์•„์š” ๋ชฉ๋ก, ๊ด€์‹ฌ๊ธ€๋กœ ๋“ฑ๋กํ•œ ๋ชฉ๋ก๊ณผ ์ข‹์•„์š” ์ƒ์„ธ์ •๋ณด, ๊ด€์‹ฌ๊ธ€ ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ค„ ๋•Œ ๋งŒ๋“ค์–ด์•ผ ํ•  ํŒŒ์ผ

    • app/posts/[id]/[slug]/page.tsx
      • /posts/1/likes -> { id: '1', slug: 'likes' }
      • /posts/2/likes -> { id: '2', slug: 'likes' }
      • /posts/2/favorites -> { id: '2', slug: 'favorites' }
    • app/posts/[id]/[slug]/[sid]/page.tsx
      • /posts/3/likes/4 -> { id: '3', slug: 'likes', sid: '4' }
      • /posts/3/favorites/4 -> { id: '3', slug: 'favorites', sid: '4' }

4.6.2 Catch-all ์„ธ๊ทธ๋จผํŠธ

  • ๋Œ€๊ด„ํ˜ธ ์•ˆ์— ์ค„์ž„ํ‘œ ...๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ํ•˜์œ„ ๊ฒฝ๋กœ๊ฐ€ ๋” ์žˆ์–ด๋„ ๋งค์นญ๋จ

  • ๋งค์นญ๋œ ๊ฐ’์€ ์ „์ฒด ํ•˜์œ„ ๊ฒฝ๋กœ๋ฅผ ํฌํ•จํ•ด์„œ params์— ๋ฐฐ์—ด๋กœ ์ €์žฅ๋จ

  • Catch-all ์„ธ๊ทธ๋จผํŠธ๋ฅผ ์ด์šฉํ•ด์„œ ํŠน์ • ๊ฒŒ์‹œ๊ธ€์— ๋‹ฌ๋ฆฐ ์ข‹์•„์š” ๋ชฉ๋ก, ๊ด€์‹ฌ๊ธ€๋กœ ๋“ฑ๋กํ•œ ๋ชฉ๋ก๊ณผ ์ข‹์•„์š” ์ƒ์„ธ์ •๋ณด, ๊ด€์‹ฌ๊ธ€ ์ƒ์„ธ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ค„ ๋•Œ ๋งŒ๋“ค์–ด์•ผ ํ•  ํŒŒ์ผ๊ณผ params ๊ฐ’

    • app/posts/[id]/[...slug]/page.tsx
      • /posts/1 -> ๋งค์นญ๋˜์ง€ ์•Š์Œ
      • /posts/1/likes -> { id: '1', slug: ['likes'] }
      • /posts/2/likes -> { id: '2', slug: ['likes'] }
      • /posts/2/favorites -> { id: '2', slug: ['favorites']}
      • /posts/3/like/4 -> { id: '3', slug: ['likes', '4'] }
      • /posts/3/favorites/4 -> { id: 3', slug: ['favorites', '4'] }

4.6.3 Optional Catch-all ์„ธ๊ทธ๋จผํŠธ

  • ํด๋”๋ช…์„ ์ด์ค‘ ๋Œ€๊ด„ํ˜ธ๋กœ ๋ฌถ์–ด์„œ ์ž‘์„ฑํ•˜๋ฉด Catch-all ์„ธ๊ทธ๋จผํŠธ๋ฅผ ์„ ํƒ์‚ฌํ•ญ์œผ๋กœ ์ง€์ •

  • ํŠน์ • ๊ฒŒ์‹œ๊ธ€๊ณผ ๋Œ“๊ธ€ ๋ชฉ๋ก, ๋Œ“๊ธ€ ์ƒ์„ธ ์ •๋ณด๋ฅผ ํ•˜๋‚˜์˜ page๋กœ ์ฒ˜๋ฆฌํ•  ๊ฒฝ์šฐ params ๊ฐ’

    • app/posts/[id]/...slug/page.tsx
      • /posts/1 -> { id: '1' }
      • /posts/2 -> { id: '2' }
      • /posts/3/replies -> { id: '3', slug: ['replies'] }
      • /posts/3/replies/2 -> { id: '3', slug: ['replies', '2'] }

4.6.3 generateStaticParams() ํ•จ์ˆ˜

  • ๋™์  ๋ผ์šฐํŠธ๋กœ ๊ตฌ์„ฑ๋œ ํŽ˜์ด์ง€์˜ params๋ฅผ ๋ฏธ๋ฆฌ ์ง€์ •ํ•ด์„œ ๋นŒ๋“œ์‹œ ํ•ด๋‹น ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ฐ€์ง€๋Š” ํŽ˜์ด์ง€๋ฅผ ์ •์ ์œผ๋กœ ์ƒ์„ฑ(SSG)

  • ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•  ์ •์  ํŽ˜์ด์ง€์˜ params๋ฅผ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ž‘์„ฑ

    // ์ด ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜ํ•œ ๋ฐฐ์—ด๋งŒํผ SSG ํŽ˜์ด์ง€๋ฅผ ๋ฏธ๋ฆฌ ์ƒ์„ฑ
    // ๋นŒ๋“œํ•˜๋ฉด .next/server/app/posts/1.html, 2.html, 3.html
    export function generateStaticParams() {
      // ๊ณต์ง€๊ธ€์— ๋Œ€ํ•œ fetch ์ž‘์—…
      const posts = [
        { id: '1', title: '1๋ฒˆ ์ œ๋ชฉ' },
        { id: '2', slug: '2', sid: '3', title: '2๋ฒˆ ์ œ๋ชฉ' },
        { id: '3', slug: '2', sid: '3', title: '4๋ฒˆ ์ œ๋ชฉ' },
      ];
    
      return posts;
    }
    
    export default async function Page({ params: { id } }){
      const resJson = await fetchPost(id);
      let data = resJson.ok ? resJson.item : null;
      return (
        ...
      );
    }
  • ๋นŒ๋“œ ํ•  ๋•Œ ๋™์ž‘ ์ˆœ์„œ

    1. ๋นŒ๋“œ์‹œ generateStaticParams() ํ•จ์ˆ˜ ํ˜ธ์ถœ ํ›„ ๋ฐ˜ํ™˜ ๋ฐ›์€ ๋ฐฐ์—ด์˜ ๊ฐ ์š”์†Œ๋ฅผ params๋กœ ๊ตฌ์„ฑํ•ด์„œ Page ์ปดํฌ๋„ŒํŠธ ํ˜ธ์ถœ
    2. Page ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ˜ํ™˜ ๋ฐ›์€ HTML์„ ๋นŒ๋“œ ๊ฒฐ๊ณผ๋กœ ์ €์žฅ(posts/1.html, 2.html, 3.html)
    3. ์ดํ›„ ๋ธŒ๋ผ์šฐ์ €์˜ posts/1 ์š”์ฒญ์ด ์˜ค๋ฉด ๋นŒ๋“œ์‹œ ๋งŒ๋“ค์–ด๋‘” ์ •์  ๋ผ์šฐํŒ… ํ…Œ์ด๋ธ”์—์„œ ๋งค์นญ๋˜๋Š” url์ด ์žˆ๋Š”์ง€ ํ™•์ธ ํ›„ posts/[id]/page.tsx ํŒŒ์ผ์„ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ณ  posts/1.html์„ ์‘๋‹ต
    4. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ posts/4 ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒฝ์šฐ ์ •์  ๋ผ์šฐํŒ… ํ…Œ์ด๋ธ”์— ๋งค์นญ๋˜๋Š” url์ด ์—†์œผ๋ฏ€๋กœ posts/[id]/page.tsx ํŒŒ์ผ์„ ์‹คํ–‰ํ•˜์—ฌ ์‘๋‹ต

4.7 ๋ผ์šฐํŠธ ๊ทธ๋ฃน ๋ฐ ํ”„๋ผ์ด๋น— ํด๋”

4.7.1 ๋ผ์šฐํŠธ ๊ทธ๋ฃน

  • app ๋ผ์šฐํ„ฐ๋Š” app ํ•˜์œ„ ํด๋”๊ฐ€ URL ๊ฒฝ๋กœ์— ๋งคํ•‘๋จ

  • ํด๋”๊ฐ€ URL ๊ฒฝ๋กœ์— ํฌํ•จ๋˜์ง€ ์•Š๊ฒŒ ํ•˜๊ณ  ์‹ถ์„๋•Œ ๋ผ์šฐํŠธ ๊ทธ๋ฃน์„ ์ƒ์„ฑ

  • (ํด๋”๋ช…) ์ฒ˜๋Ÿผ ํด๋”๋ช…์— ()๋ฅผ ๋ถ™์—ฌ์„œ ์ž‘์„ฑ

  • URL์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๊ณ  ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€๋ฅผ ํ•˜๋‚˜์˜ ํด๋”์— ๋ฌถ์–ด์„œ ๊ด€๋ฆฌ

  • ๋‹ค๋ฅธ ๋ผ์šฐํŠธ ๊ทธ๋ฃน์— ๋™์ผํ•œ ํ•˜์œ„ ๊ฒฝ๋กœ ์ž‘์„ฑ์‹œ ์ปดํŒŒ์ผ ์—๋Ÿฌ ๋ฐœ์ƒ

    project-root/
    โ”œโ”€โ”€ app/
    โ”‚   โ”œโ”€โ”€(user)/
    โ”‚   โ”‚   โ”œโ”€โ”€ login/
    โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ page.tsx
    โ”‚   โ”‚   โ”œโ”€โ”€ signup/
    โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ page.tsx
    

user ํด๋”๊ฐ€ ๋ผ์šฐํŠธ ๊ฒฝ๋กœ์— ํฌํ•จ๋˜๋Š” ๊ฒฝ์šฐ

  • login, signup ํด๋”๋ฅผ user ํด๋” ํ•˜์œ„๋กœ ๊ทธ๋ฃนํ™” ํ•ด์„œ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ ๊ฒฝ๋กœ์— user๊ฐ€ ํฌํ•จ๋จ

    project-root/
    โ”œโ”€โ”€ app/
    โ”‚   โ”œโ”€โ”€ user/
    โ”‚   โ”‚   โ”œโ”€โ”€ login/
    โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ page.tsx
    โ”‚   โ”‚   โ”œโ”€โ”€ signup/
    โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ page.tsx
    
    • /user/login -> app/user/login/page.tsx
    • /user/signup -> app/user/signup/page.tsx

๊ฒฝ๋กœ์—์„œ user๋ฅผ ์ œ์™ธํ•˜๊ธฐ ์œ„ํ•ด ํด๋”๋ฅผ ์ œ๊ฑฐํ•œ ๊ฒฝ์šฐ

  • ๊ฒฝ๋กœ์—์„œ user๋ฅผ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด์„œ user ํด๋”๋ฅผ ์ œ๊ฑฐ

    project-root/
    โ”œโ”€โ”€ app/
    โ”‚   โ”œโ”€โ”€ login/
    โ”‚   โ”‚   โ””โ”€โ”€ page.tsx
    โ”‚   โ”œโ”€โ”€ signup/
    โ”‚   โ”‚   โ””โ”€โ”€ page.tsx
    
    • /login -> app/login/page.tsx
    • /signup -> app/signup/page.tsx

user ํด๋”๋ฅผ ์ œ๊ฑฐํ•˜์ง€ ์•Š๊ณ  ๊ฒฝ๋กœ์—์„œ ์ œ๊ฑฐํ•˜๊ณ  ์‹ถ์„ ๋•Œ

  • ๋ผ์šฐํŠธ ๊ทธ๋ฃน์œผ๋กœ ๊ด€๋ฆฌ

    project-root/
    โ”œโ”€โ”€ app/
    โ”‚   โ”œโ”€โ”€(user)/
    โ”‚   โ”‚   โ”œโ”€โ”€ login/
    โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ page.tsx
    โ”‚   โ”‚   โ”œโ”€โ”€ signup/
    โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ page.tsx
    
    • /login -> app/(user)/login/page.tsx
    • /signup -> app/(user)/signup/page.tsx

๋ผ์šฐํŠธ ๊ทธ๋ฃน ํ•˜์œ„์— layout ์ž‘์„ฑ์‹œ ๋ผ์šฐํŠธ ๊ทธ๋ฃน ๋‚ด๋ถ€ ํŽ˜์ด์ง€์—๋งŒ ์ ์šฉ

  • ๋™์ผํ•œ URL depth์— ์žˆ๋Š” ํŽ˜์ด์ง€์— ๋‹ค๋ฅธ layout์„ ์ ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ๋Š” ๋ผ์šฐํŠธ ๊ทธ๋ฃน์„ ๊ฐ๊ฐ ๋งŒ๋“ค๊ณ  layout ์ž‘์„ฑ

account, cart, checkout ํŽ˜์ด์ง€์—์„œ account, cart์—๋งŒ ๋™์ผํ•œ layout์„ ์ ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ

๋ฃจํŠธ ๋ ˆ์ด์•„์›ƒ์„ ์—ฌ๋Ÿฌ๊ฐœ ์ •์˜ํ•˜๊ณ  ์‹ถ์„ ๋•Œ

4.7.2 ํ”„๋ผ์ด๋น— ํด๋”

  • _๋กœ ์‹œ์ž‘ํ•˜๋Š” ํด๋”๋Š” page ํŒŒ์ผ์ด ์žˆ์–ด๋„ ๋ผ์šฐํŒ…์—์„œ ์ œ์™ธ
  • ํ™œ์šฉ ์‚ฌ๋ก€
    • UI ๋กœ์ง๊ณผ ๋ผ์šฐํŒ… ๋กœ์ง ๋ถ„๋ฆฌ
    • ํ”„๋กœ์ ํŠธ ๋ฐ Next.js ์ƒํƒœ๊ณ„ ์ „๋ฐ˜์— ๊ฑธ์ณ ๋‚ด๋ถ€ ํŒŒ์ผ์„ ์ผ๊ด€๋˜๊ฒŒ ๊ตฌ์„ฑ
    • ์ฝ”๋“œ ํŽธ์ง‘๊ธฐ์—์„œ ํŒŒ์ผ ๋ถ„๋ฅ˜ ๋ฐ ๊ทธ๋ฃนํ™”

4.8 ๋ผ์šฐํŒ… ์ž‘๋™ ๋ฐฉ์‹

4.8.1 ์ฝ”๋“œ ๋ถ„ํ• 

  • ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฒฝ๋กœ๋ณ„๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์€ ๋ฒˆ๋“ค๋กœ ๋ถ„ํ• ํ•ด์„œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋ฐ์ดํ„ฐ์˜ ์–‘๊ณผ ์‘๋‹ต์‹œ๊ฐ„์ด ์ค„์–ด๋“ค์–ด ์„ฑ๋Šฅ ํ–ฅ์ƒ
  • ๋นŒ๋“œ ํ›„ .next/server/app ํด๋”์—์„œ ํ™•์ธ

4.8.2 Prefetching

  • <Link> ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋งํฌ๋ฅผ ๋ˆ„๋ฅด๊ธฐ ์ „์— ํŽ˜์ด์ง€๋ฅผ ๋ฏธ๋ฆฌ ๋กœ๋“œํ•˜๋Š” ์ž‘์—…
  • ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋งŒ ํ™œ์„ฑํ™” ๋จ

<Link> ์ปดํฌ๋„ŒํŠธ์˜ prefetch ์†์„ฑ์— ๋”ฐ๋ฅธ ๋™์ž‘

  • false: prefetch ๋™์ž‘ ์•ˆํ•จ

  • null(๊ธฐ๋ณธ๊ฐ’)

    • ์ •์  ๋ผ์šฐํŠธ์ผ ๊ฒฝ์šฐ ๋งํฌ๊ฐ€ ํ™”๋ฉด์— ๋ณด์ผ ๋•Œ(๋ทฐํฌํŠธ์— ๋“ค์–ด์˜ฌ ๋•Œ) ์ „์ฒด ํŽ˜์ด์ง€๊ฐ€ ํ”„๋ฆฌํŒจ์น˜๋˜์–ด ์บ์‹œ๋จ
    • ๋™์  ๋ผ์šฐํŠธ์ผ ๊ฒฝ์šฐ ๋งํฌ๊ฐ€ ํ™”๋ฉด์— ๋ณด์ผ ๋•Œ ๋ Œ๋”๋ง๋œ ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ์—์„œ ์ฒซ๋ฒˆ์งธ loading.tsx ํŒŒ์ผ์ด ๋‚˜ํƒ€๋‚  ๋•Œ๊นŒ์ง€๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ€์ ธ์˜ด(30์ดˆ ๋™์•ˆ ์บ์‹œ๋จ)
      • ์‹ค์ œ ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•  ๋•Œ(ํด๋ฆญ ํ•  ๋•Œ) ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์ฆ‰์‹œ ๋ณด์—ฌ ์ฃผ๊ณ  ์ดํ›„์˜ ๋‚ด์šฉ์„ ๊ฐ€์ ธ์˜ด
  • true

    • ๋งํฌ๊ฐ€ ํ™”๋ฉด์— ๋ณด์ด์ง€ ์•Š์•„๋„(DOM์— ์กด์žฌํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด) ์ •์  ๋ผ์šฐํŠธ์™€ ๋™์  ๋ผ์šฐํŠธ ๋ชจ๋‘ ๋‹ค ์ „์ฒด ํŽ˜์ด์ง€๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ€์ ธ์˜ด(5๋ถ„ ๋™์•ˆ ์บ์‹œ๋จ)
  • ํ”„๋ฆฌํŒจ์นญ๋œ ๋ฐ์ดํ„ฐ์™€ ๋ ˆ์ด์•„์›ƒ์€ 30์ดˆ ๋™์•ˆ ๋ผ์šฐํ„ฐ ์บ์‹œ์— ์ €์žฅ๋จ

    • ๋ผ์šฐํ„ฐ ์บ์‹œ๋Š” ๋น„ํ™œ์„ฑํ™” ์‹œํ‚ฌ ์ˆ˜ ์—†์Œ
    • router.refresh() ํ˜ธ์ถœ ์‹œ ๋ผ์šฐํ„ฐ ์บ์‹œ ์‚ญ์ œ

4.8.3 ๋ถ€๋ถ„ ๋ Œ๋”๋ง

  • ํŽ˜์ด์ง€ ์ด๋™์‹œ ๊ณต์œ  ๋ ˆ์ด์•„์›ƒ์€ ์œ ์ง€ํ•œ ์ฑ„๋กœ ๋ณ€๊ฒฝ๋œ ํŽ˜์ด์ง€๋งŒ ๋ Œ๋”๋ง
  • /posts/3 -> /posts/2๋กœ ์ด๋™์‹œ app/layout.tsx, app/posts/layout.tsx๋Š” ๋‹ค์‹œ ๋ Œ๋”๋ง ํ•˜์ง€ ์•Š์Œ

4.8.4 ์ด์ „/์ดํ›„ ํŽ˜์ด์ง€ ์ด๋™

  • ์Šคํฌ๋กค์„ ์œ ์ง€ํ•˜๊ณ  ๋ผ์šฐํ„ฐ ์บ์‹œ๋ฅผ ์žฌ์‚ฌ์šฉ

4.9 ํ”„๋กœ์ ํŠธ ๊ตฌ์„ฑ ๋ฐ ๋ผ์šฐํŠธ ๊ด€๋ฆฌ

  • ๋ผ์šฐํŒ… ํด๋” ๋‚ด์— page, route ํŒŒ์ผ์ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ๋ผ์šฐํŒ… ๋จ
  • page์™€ route ํŒŒ์ผ๋งŒ ๋ผ์šฐํŒ… ๋จ

4.9.1 ํ”„๋กœ์ ํŠธ ํด๋” ๊ตฌ์กฐ ์ „๋žต

  • ํ”„๋กœ์ ํŠธ ํŒŒ์ผ๊ณผ ํด๋”๋ฅผ ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑํ•  ๊ฒƒ์ธ์ง€์— ๋Œ€ํ•ด์„œ ์˜ฌ๋ฐ”๋ฅด๊ฑฐ๋‚˜ ํ‹€๋ฆฐ ๋ฐฉ๋ฒ•์€ ์—†์Œ
  • ์—ฌ๋Ÿฌ ์ „๋žต ์ค‘ ํŒ€์—๊ฒŒ ์ ํ•ฉํ•œ ๋ฐฉ์‹์„ ์„ ํƒํ•˜๊ณ  ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•ด์•ผ ํ•จ

ํ”„๋กœ์ ํŠธ ํŒŒ์ผ์„ app ์™ธ๋ถ€์— ์ €์žฅ

  • app ํด๋”๋Š” ๋ผ์šฐํŒ…์œผ๋กœ๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋Š” app ํด๋” ์™ธ๋ถ€์— ์ €์žฅ

ํ”„๋กœ์ ํŠธ ํŒŒ์ผ์„ app ๋‚ด๋ถ€์— ์ €์žฅ

  • ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ app ํด๋” ๋‚ด๋ถ€์— ์ €์žฅ

๊ธฐ๋Šฅ์ด๋‚˜ ๊ฒฝ๋กœ๋ณ„๋กœ ํŒŒ์ผ ๋ถ„ํ• 

  • ๊ณต์šฉ ์ปดํฌ๋„ŒํŠธ๋‚˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ app ํด๋” ํ•˜์œ„์— ์ž‘์„ฑํ•˜๊ณ  ๊ฐ ํŽ˜์ด์ง€๋ณ„๋กœ ์‚ฌ์šฉํ•  ์ปดํฌ๋„ŒํŠธ๋‚˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๊ฐ ํŽ˜์ด์ง€ ํด๋” ํ•˜์œ„์— ์ž‘์„ฑ

๐Ÿ“œ ch10 02

  • ๐Ÿ’ป 02
โš ๏ธ **GitHub.com Fallback** โš ๏ธ