NextJS Essential 3 - Navigating
Linking and Navigating
ref : https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating
Link Component
import Link from 'next/link'
 
export default function Page() {
  return <Link href="/dashboard">Dashboard</Link>
}
Server
서버측에서 Route 변경할때는 아래 함수를 사용한다.
Server Components, Route Handlers, Server Actions 사용 가능
- redirect
- permanentRedirect
redirect
https://nextjs.org/docs/app/api-reference/functions/redirect
// eg) 서버 컴포넌트에서 데이터가 없는 경우 redirect
import { redirect } from 'next/navigation'
 
async function fetchTeam(id) {
  const res = await fetch('https://...')
  if (!res.ok) return undefined
  return res.json()
}
 
export default async function Profile({ params }) {
  const team = await fetchTeam(params.id)
  if (!team) {
    redirect('/login')
  }
}
permanentRedirect
https://nextjs.org/docs/app/api-reference/functions/permanentRedirect
// eg) 사용자 정보 업데이트 > 캐시 revalidate > redirect
'use server'
import { permanentRedirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
 
export async function updateUsername(username: string, formData: FormData) {
  try {
    // Call database
  } catch (error) {
    // Handle errors
  }
 
  revalidateTag('username') // Update all references to the username
  permanentRedirect(`/profile/${username}`) // Navigate to the new user profile
}
----
// eg) DB, API 조회 후 데이터가 없으면 /으로 리다이렉트 ( NOT FOUND 대신 홈으로 이동 시키고 싶은 경우 ) 
export default async function Playlist(props) {
  const { params, searchParams } = props;
  const playList = await getPlayListFromServer(searchParams?.list);
  if (!playList) permanentRedirect(`/`); // Navigate to the new user profile
  ...
}
Client
클라이언트 컴포넌트에서 사용할 수 있는 훅들이다.
- useRouter
- useParams
- usePathname
- useSearchParams
useRouter
https://nextjs.org/docs/app/api-reference/functions/use-router
- 'use client' 을 써야 된다. > useRouter
 
- Server Components, you would redirect() instead.
 
- cf) next/router 은 사용하지 않는다.
// eg) navigate another page 
'use client'
import { useRouter } from 'next/navigation'
 
export default function Page() {
  const router = useRouter()
  // router.push  
  // router.replace
  // router.back
  // router.
 
  return (
    <button type="button" onClick={() => router.push('/dashboard')}>
      Dashboard
    </button>
  )
}
// eg) navigate external page
<Link
   className="font-medium underline underline-offset-4"
   href="https://github.com/vercel/ai-chatbot"
   target="_blank"
 >
   open source
 </Link>{" "}
useParams
'use client'
 
import { useParams } from 'next/navigation'
 
export default function ExampleClientComponent() {
  const params = useParams<{ tag: string; item: string }>()
  // Route -> /shop/[tag]/[item]
  // URL -> /shop/shoes/nike-air-max-97
  // `params` -> { tag: 'shoes', item: 'nike-air-max-97' }
  console.log(params)
 
  return <></>
}
usePathname, useSearchParams
'use client'
import { usePathname, useSearchParams } from 'next/navigation'
 
function ExampleClientComponent() {
  const pathname = usePathname()
  // URL      >>. Returned value
  // /dashboard?v=2  >>   '/dashboard'
  // /blog/hello-world  >>  '/blog/hello-world'
  function switchLocale(locale: string) {
    // e.g. '/en/about' or '/fr/contact'
    const newPath = `/${locale}${pathname}`
    window.history.replaceState(null, '', newPath)
  }
  // URL -> `/dashboard?search=my-project`
  // `search` -> 'my-project'
  const searchParams = useSearchParams()
  const search = searchParams.get('search')
}
notfound
- 적절한 리소스가 없는 경우, 404 not found 페이지로 이동한다.
// [product-id]/not-found.tsx
import { Button } from "@/components/ui/button";
import Link from "next/link";
export default function NotFound() {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen bg-background text-foreground">
      <h1 className="text-4xl font-bold mb-4">페이지를 찾을 수 없어요</h1>
      <p className="text-xl mb-8">찾고 계신 페이지가 존재하지 않아요.</p>
      <Button asChild>
        <Link href="/">홈으로 돌아가기</Link>
      </Button>
    </div>
  );
}
---
// [product-id]/page.tsx
import { notFound } from "next/navigation";
export default async function Page({ params }: PageProps) {
  const productId = params?.["product-id"];
  const product = await getProductById(Number(productId));
  if (!product) notFound();
  return (
    <div>
      <ProductDetail product={product} />
    </div>
  );
}
Window
replaceState
window.history.replaceState({}, "", `/chat/${chatId}`);
Server Routes 타입별 랜더링
Dynamic Routes (SSR)
https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes#example
interface LibraryProps {
  params: {
    id: string;
  };
}
const sleep = async (ms) => new Promise((res) => setTimeout(res, ms));
export default async function Library({ params }: LibraryProps) {
  console.log("-->params.id", params.id);
  await sleep(2500);
  console.log("--> get some data from server");
  return (<div />)
}
generateStaticParams (SSG)
https://nextjs.org/docs/app/api-reference/functions/generate-static-params
// app/blog/[slug]/page.js
// Return a list of `params` to populate the [slug] dynamic segment
export async function generateStaticParams() {
  const posts = await fetch('https://.../posts').then((res) => res.json())
  return posts.map((post) => ({    slug: post.slug,  }))
}
 
// Multiple versions of this page will be statically generated
// using the `params` returned by `generateStaticParams`
export default function Page({ params }) {
  const { slug } = params
  // ...
}
Parallel Routes
https://nextjs.org/docs/app/building-your-application/routing/parallel-routes

Intercepting Routes
https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes