[Next.js] Redirect ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ• 5๊ฐ€์ง€

Next.js์—์„œ ๋Œ€์‘์ด ๋˜์–ด ์žˆ์ง€ ์•Š์€ `/` path๋กœ ์ง„์ž…ํ–ˆ์„ ๋•Œ default `/home` page๋กœ redirect ํ•˜๋Š” ์ŠคํŽ™์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

Redirect ์‹œ ์‚ฌ์šฉํ•˜๋Š” ์ƒํƒœ์ฝ”๋“œ

Next.js์—์„œ์˜ redirect ๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „ redirect ์‹œ์— ์„œ๋ฒ„์—์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ƒํƒœ์ฝ”๋“œ๋ฅผ ๋จผ์ € ์‚ดํŽด๋ณด์ž.

300๋ฒˆ ๋Œ€์˜ ์ƒํƒœ์ฝ”๋“œ๋Š” ๋ชจ๋‘ Redirection ์„ ์˜๋ฏธํ•˜๋ฉฐ, 300 ~ 308 ๊นŒ์ง€ ์žˆ๋‹ค.

 

  • 301 (Moved Permanently) : ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ ๋ฉ”์†Œ๋“œ๋ฅผ GET ์œผ๋กœ ๋ฐ”๊พธ๋ฉฐ, redirect ์ฒ˜๋ฆฌ๊ฐ€ ์บ์‹ฑ๋œ๋‹ค.
  • 302 (Found - Moved Temporarily) :  ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ ๋ฉ”์†Œ๋“œ๋ฅผ GET ์œผ๋กœ ๋ฐ”๊พธ๋ฉฐ, redirect ์ฒ˜๋ฆฌ๊ฐ€ ์ผ์‹œ์ ์ด๊ณ  ์บ์‹ฑ๋˜์ง€ ์•Š๋Š”๋‹ค.
  • 304 (Not Modified) : ๋งˆ์ง€๋ง‰ ์š”์ฒญ ์ดํ›„ ์‘๋‹ต์ด ๋ฐ”๋€Œ์ง€ ์•Š์•˜์„ ๋•Œ 
  • 307 (Temporary Redirect) : ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ ๋ฉ”์†Œ๋“œ๋ฅผ ๋ฐ”๊พธ์ง€ ์•Š์œผ๋ฉฐ, redirect ์ฒ˜๋ฆฌ๊ฐ€ ์ผ์‹œ์ ์ด๊ณ  ์บ์‹ฑ๋˜์ง€ ์•Š๋Š”๋‹ค.
  • 308 (Permanent Redirect) : ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ ๋ฉ”์†Œ๋“œ๋ฅผ ๋ฐ”๊พธ์ง€ ์•Š์œผ๋ฉฐ, redirect ์ฒ˜๋ฆฌ๊ฐ€ ์บ์‹ฑ๋œ๋‹ค.

 

304๋ฅผ ์ œ์™ธํ•˜๊ณ  (301๊ณผ 308), (302์™€ 307)์ด ๋‹ฎ์•„์žˆ๋Š”๋ฐ ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ ๋ฉ”์†Œ๋“œ๋ฅผ ๋ฐ”๊พธ๋Š๋ƒ ์•„๋‹ˆ๋ƒ์— ๋”ฐ๋ผ ๊ตฌ๋ถ„๋œ๋‹ค.

Next.js ์—์„œ Redirect ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•

Next.js ์—์„œ redirect ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์€ 3๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

 

next.config.js ํŒŒ์ผ์—์„œ redirects ํ•จ์ˆ˜๋ฅผ ์„ธํŒ…ํ•œ๋‹ค.

๊ณต์‹ ์‚ฌ์ดํŠธ์— ๊ธฐ์žฌ๋˜์–ด ์žˆ๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ next config ํŒŒ์ผ์— redirects ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค.

module.exports = {
  async redirects() {
    return [
      {
        source: '/',
        destination: '/home',
        permanent: true,
      },
    ]
  },
}

 

source ๋กœ ๋“ค์–ด์˜จ ์œ ์ €๋ฅผ destination ์œผ๋กœ redirect ์‹œ์ผœ์ฃผ๋ฉฐ, Parmanent ๋ฅผ true๋กœ ์„ธํŒ…ํ•˜๋ฉด 308 ์ƒํƒœ์ฝ”๋“œ๋ฅผ / false๋กœ ์„ธํŒ…ํ•˜๋ฉด 307 ์ƒํƒœ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ ๋ฉ”์†Œ๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด 301, 302๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. dynamic path๋ฅผ ๋Œ€์‘ํ•˜๊ธฐ ์œ„ํ•ด ์™€์ผ๋“œ์นด๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, regex ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ์‹ฌ์ง€์–ด ์ฟ ํ‚ค๋‚˜ ํ—ค๋”, ์ฟผ๋ฆฌ์˜ ์ƒํƒœ์— ๋”ฐ๋ผ redirect ๊ฐ€ ๋™์ž‘ํ•˜๋„๋ก ์„ธํŒ…ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์ž.

 

 

ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ์˜ ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ ์ƒ์—์„œ next router์˜ ํ•จ์ˆ˜๋กœ ์ด๋™ํ•œ๋‹ค.

2-1. next.js ๋ฒ„์ „ 13 ์ด์ƒ์ผ ๊ฒฝ์šฐ

๊ณต์‹ ์‚ฌ์ดํŠธ์— ๊ธฐ์žฌ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ฒ„์ „ 13 ์ดํ›„์— ์ƒ๊ธด next/navigation ์˜ redirect ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

import { redirect } from 'next/navigation';

export default function Default() {
  redirect('/home');
  // permanentRedirect('/home');
}

์ด ๋˜ํ•œ 307 ์ƒํƒœ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, permanentRedirect ๋ฅผ ์ด์šฉํ•˜๋ฉด 308 ์ƒํƒœ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค.

 

2-2. next.js ๋ฒ„์ „ 13 ๋ฏธ๋งŒ์ผ ๊ฒฝ์šฐ

๊ณต์‹ ์‚ฌ์ดํŠธ์— ๊ธฐ์žฌ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ฒ„์ „ 13 ์ด์ „์˜ ๊ฒฝ์šฐ next/router ์˜ replace ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

import { useRouter } from 'next/router';
 
export default function Default() {
  const router = useRouter();
  router.replace('/home');
  return null;
}

 

`/` path์— ํ•ด๋‹นํ•˜๋Š” ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ์ธ `pages/index.js` ๋ฅผ ๋งŒ๋“  ํ›„ ํ•ด๋‹น ํŽ˜์ด์ง€์—์„œ router.replace ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค. ์ด ๊ฒฝ์šฐ ์„œ๋ฒ„๊ฐ€ redirect ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•œ ๊ฒƒ์ด ์•„๋‹Œ ํด๋ผ์ด์–ธํŠธ์—์„œ GET ์š”์ฒญ์„ 2๋ฒˆ ํ•œ ๊ฒƒ์ด ๋œ๋‹ค.

 

ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ์˜ ์„œ๋ฒ„ ์ฝ”๋“œ ์ƒ์—์„œ ctx.res ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ด๋™ํ•œ๋‹ค.

ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ์˜ getServerSideProps ๋‚˜ getStaticProps ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ctx.res ๋ฅผ ์ด์šฉํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์— ๋„๋‹ฌํ•˜๊ธฐ ์ „์— redirect ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

const NotFound = () => {
  return null;
};

export async function getServerSideProps({ res }) {
  res.writeHead(302, { Location: '/home' });
  res.end();

  return {
    props: {}, // you can return props if needed
  };
}

export default NotFound;

 

App.tsx ์˜ getInitialProps ์—์„œ ctx.res ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ด๋™ํ•œ๋‹ค.

App.getInitialProps = async ({ ctx }: AppContext) => {
  const { res } = ctx;
  res?.writeHead(307, { Location: '/404' });
  res?.end();
  return {};
}

ํ•˜์ง€๋งŒ getInitialProps ๋Š” ํ•จ์ˆ˜ ํ˜ธ์ถœ ์‹œ์ ์ด ๋ช…ํ™•ํ•˜์ง€ ์•Š์€ Legacy API์ด๋ฏ€๋กœ ๋˜๋„๋ก์ด๋ฉด ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. (๊ณต์‹ ๋ฌธ์„œ ์ฐธ๊ณ )

 

ํด๋ผ์ด์–ธํŠธ ์„œ๋ฒ„ ์ฝ”๋“œ์—์„œ res.redirect ๋ฅผ ์ด์šฉํ•˜์—ฌ ์ด๋™ํ•œ๋‹ค.

import express from 'express';

const app = express();

app.get('/', (_, res) => {
  res.redirect(307, '/home');
});

 

์ด์ •๋ฆฌ & ์ถ”์ฒœ

  1. next.config.js ์„ธํŒ… โœ…
  2. ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ ์ฝ”๋“œ ์ƒ์—์„œ ์ด๋™ -> Redirect ๋กœ ํ‘œ์‹œ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.
  3. getServerSideProps / getStaticProps ์—์„œ ์ด๋™ โœ…
  4. getInitialProps ์—์„œ ์ด๋™ -> Legacy API ์ด๋ฏ€๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.
  5. ํด๋ผ์ด์–ธํŠธ ์„œ๋ฒ„ ์ฝ”๋“œ์—์„œ ์ด๋™ โœ…

 

1, 3, 5๋ฒˆ์ด ๋ชจ๋‘ ์„ค์ •๋˜์–ด ์žˆ์„ ๊ฒฝ์šฐ 5 > 1 > 3 ์˜ ์šฐ์„ ์ˆœ์œ„๋กœ ์ ์šฉ๋œ๋‹ค.

Redirect ๋Š” ์›ฌ๋งŒํ•˜๋ฉด ๊ธฐ์กด ํŽ˜์ด์ง€์˜ ์˜ํ–ฅ์ด ์žˆ๊ธฐ ์ „์— ์ˆ˜ํ–‰๋˜๋Š” ๊ฒƒ์ด ์ข‹๊ธฐ ๋•Œ๋ฌธ์— ์šฐ์„ ์ˆœ์œ„ ์ˆœ์œผ๋กœ ์ ์šฉํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค. ํ•˜์ง€๋งŒ 1์˜ ๊ฒฝ์šฐ ์ƒํƒœ์ฝ”๋“œ๋ฅผ 301, 302 ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์—†์Œ์„ ์œ ์˜ํ•ด์•ผ ํ•œ๋‹ค.

 

๋”ํ•ด์„œ SEO์— ๊ด€ํ•˜์—ฌ 301์„ ์ƒํƒœ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ธฐ์กด URL๊ณผ ์ƒˆ๋กœ์šด URL์„ ๋™์ผ์‹œํ•˜์—ฌ ๊ถŒํ•œ ๋ฐ ๊ด€๋ จ์„ฑ ์œ ์ง€์— ๋„์›€์ด ๋œ๋‹ค. ํ•˜์ง€๋งŒ ๊ผญ 301๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๊ฐ ์ƒํ™ฉ์— ๋งž๋Š” ์ƒํƒœ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.

๋ฐ˜์‘ํ˜•