šŸ™ˆ

怐šŸ‘Šęš“åŠ›ć€‘Next.jsć§ćƒ¬ćƒ³ćƒ€ćƒŖćƒ³ć‚°ę™‚ć«ć‚ć‹ć‚‹NotFoundć‚’ććé›‘ć«åƾåæœć™ć‚‹

2022/12/08ć«å…¬é–‹ćƒ»ē“„5,300字

ćƒ¬ćƒ³ćƒ€ćƒŖćƒ³ć‚°ę™‚ć«ć‚ć‹ć‚‹Not FoundćØćÆ

恓恮čؘäŗ‹ć§ćÆ态仄äø‹ć®ć‚ˆć†ćŖēŠ¶ę³äø‹ć§ćć®ćƒšćƒ¼ć‚ø恌Not Found恧恂悋恓ćØćŒć‚ć‹ć£ćŸćØćć®åƾåæœć®ä»•ę–¹ć‚’č§£čŖ¬ć—ć¾ć™ļ¼

  • ReduxćŖ恩悒ä½æć£ć¦ć„ć¦ć€getServerSidePropsć‚’å®Œå…Ø恫ä½æ恄恓ćŖć—ć¦ć„ć‚‹ę„Ÿć˜ć§ćÆćŖ恄
    • getServerSideProps恮ꮵ階恧404悒čæ”ć™ć¹ćć‹ć©ć†ć‹åˆ¤ę–­ć§ććŖć„å®Ÿč£…ć‚’ć—ć¦ć„ć‚‹
  • 動ēš„ćƒ«ćƒ¼ćƒ†ć‚£ćƒ³ć‚°ć•ć‚Œć‚‹ćƒšćƒ¼ć‚øć§ć€ćƒ¬ćƒ³ćƒ€ćƒŖćƒ³ć‚°ę™‚ć«åÆ¾č±”ć®ćƒ‡ćƒ¼ć‚æ恌ćŖ恄恓ćØćŒåˆć‚ć¦ć‚ć‹ć‚‹
pages/items/[itemId].tsx
export default function ItemShow() {
  const { query } = useRouter()
  const item = useSelector(state => state.items[query.itemId])
  
  if (item == null) {
    // ć‚¢ćƒƒā—ā—ā— č©²å½“ć™ć‚‹ć‚¢ć‚¤ćƒ†ćƒ ćÆć‚ć‚Šć¾ć›ć‚“ć§ć—ćŸā—ā— ć‚“ć‚·ćƒ„ć‚¦ć‚·ćƒ§ć‚¦ā—ā—
  }
  
  return <>ćŠę±‚ć‚ć®ćƒ‡ćƒ¼ć‚æćÆć‚ć‚Šć¾ć™ļ¼ļ¼ļ¼ļ¼ļ¼</>
}

ć“ć®ć‚³ćƒ¼ćƒ‰ć§item == null恮ćØćć«404 Not Found悒čæ”ć—ćŸć„ć§ć™ćŒć€Next.js恧ćÆgetInitialProps悄getServerSidePropsć®éš›ć«ć—ć‹ć‚¹ćƒ†ćƒ¼ć‚æć‚¹ć‚³ćƒ¼ćƒ‰ć‚’čØ­å®šć§ććŖć„ć®ć§ć€ćƒ¬ćƒ³ćƒ€ćƒŖćƒ³ć‚°ć«å…„ć£ć¦ć—ć¾ć£ćŸć‚ćŖ恟ćÆč„³ę­»ć§200 Found(大嘘)ćØčØ€ć†äŗ‹ć—ć‹ć§ćć¾ć›ć‚“ļ¼

恆恝恤恍ā—ćŠć‹ć‚ć•ć‚“ć«ćŠå‰ć®ć‚µć‚¤ćƒˆć®Googleꤜē“¢ēµęžœć‚’č¦‹ć‚‰ć‚Œć¦ę³£ć‹ć‚Œć‚ā—ā—

Not Found恮ćØćć«ćƒ¬ćƒ³ćƒ€ćƒŖćƒ³ć‚°ć‚’äø­ę–­ć™ć‚‹

ćŖ恮恧Not Found恧恂悋恓ćØćŒć‚ć‹ć£ćŸć‚‰ć¾ćšćÆćƒ¬ćƒ³ćƒ€ćƒŖćƒ³ć‚°ć‚’äø­ę–­ć—ć¾ć—ć‚‡ć†ć€‚
ć‚«ćƒ³ć®č‰Æć„ę–¹ćÆćŠę°—ć„ćć‹ćØę€ć„ć¾ć™ćŒć€throwć—ć¾ć™ć€‚

pages/items/[itemId].tsx
  if (item == null) {
    // ć‚¢ćƒƒā—ā—ā— č©²å½“ć™ć‚‹ć‚¢ć‚¤ćƒ†ćƒ ćÆć‚ć‚Šć¾ć›ć‚“ć§ć—ćŸā—ā— ć‚“ć‚·ćƒ„ć‚¦ć‚·ćƒ§ć‚¦ā—ā—
    throw new NotFoundError('恂ćŖćŸćŒå–‰ć‹ć‚‰ć‚¢ć‚·ć‚«ćŒå‡ŗć‚‹ć»ć©ę¬²ć—ćŒć‚Šć€ćć®é¢å½±ć‚’čæ½ć£ć¦ćŸć©ć‚Šē€ć„ćŸć“ć®ćƒ‡ćƒ¼ć‚æćÆ态悂恆恓恮äø–ć«ć‚ć‚Šć¾ć›ć‚“ć€‚ć‚ć„ć¤ćŒé£Ÿć¹ć¦ć—ć¾ć„ć¾ć—ćŸć€‚')
  }

NotFoundErrorćÆ仄äø‹ć®ć‚ˆć†ćŖ例外ć‚Æćƒ©ć‚¹ć‚’é©å½“ć«ä½œć£ć¦ćŠćć¾ć—ć‚‡ć†

RoutingErrors.ts
export class RouteError extends Error {
  constructor(
    public message: string,
    // 例外ć‚Æćƒ©ć‚¹ć«statusCodeć‚’ć‚‚ćŸć›ć¦ćŠć
    public readonly statusCode: number = 500
  ) {
    super(message);
  }
}

// 404 Not Found
export class NotFoundError extends RouteError {
  constructor(message: string) {
    super(message, 404);
  }
}

ꊕ恒恟ć‚Øćƒ©ćƒ¼ć‚’Custom Error Pageć§ćƒćƒ³ćƒ‰ćƒŖćƒ³ć‚°ć™ć‚‹

ćƒšćƒ¼ć‚ø恋悉ꊕ恒悉悌恟Not Foundć‚Øćƒ©ćƒ¼ć‚’Custom Error Pageć§å—ć‘å–ć£ć¦ćƒćƒ³ćƒ‰ćƒŖćƒ³ć‚°ć—ć¾ć™ć€‚
恟恠态Next.jsć®ęŒ™å‹•äøŠć‹ćŖć‚Šćƒćƒƒć‚­ćƒ¼ćŖć‚³ćƒ¼ćƒ‰ćŒåæ…č¦ć§ć™ć€ć—ć‚“ć©ć„ć­ć‡ć‡ćˆćˆćˆć€œć€œć€œć€œļ¼Ÿ

pages/_error.tsx
import { RouteError } from '@/iitokoro/RoutingErrors.ts';

type Props = {
  __errorInSSR?: true;
  statusCode: number;
  message?: string;
};

export default function ErrorPage({ statusCode, message }: Props) {
  return (
    <YourErrorPage>
      {statusCode} {message}
    </YourErrorPage>
  );
}

ErrorPage.getInitialProps = async (ctx): Promise<Props> => {
  const { res, err } = ctx;

  if (typeof window !== 'undefined') {
    const nextData = JSON.parse(document.getElementById('__NEXT_DATA__')!.innerHTML);

    // SSRꙂ恫Errorćƒšćƒ¼ć‚øćŒćƒ¬ćƒ³ćƒ€ćƒŖćƒ³ć‚°ć•ć‚Œć‚‹å‰ć€CSR恧恓恮getInitialProps恌悂恆äø€åŗ¦ć‚³ćƒ¼ćƒ«ć•ć‚Œ
    // ctx.error恫ęø”ć£ć¦ćć‚‹ć‚Øćƒ©ćƒ¼ęƒ…å ±ćŒć€Next.jsć«ć‚ˆć£ć¦ćƒ©ćƒƒćƒ—ć•ć‚ŒćŸ500ć‚Øćƒ©ćƒ¼ć«ćŖć£ć¦ć—ć¾ć†ć®ć§
    // SSRꙂ恫čæ”ć•ć‚ŒćŸpagePropsć‚’å–ć‚Šć«ć„ć£ć¦čæ”ć™ć‚ˆć†ć«ć™ć‚‹

    // ć‚Æćƒ©ć‚¤ć‚¢ćƒ³ćƒˆć‚µć‚¤ćƒ‰ć§ć®å®Ÿč”Œę™‚ć‚Øćƒ©ćƒ¼ćŒē™ŗē”Ÿć—ćŸćØ恍ćÆinitialProps.__vhErrorInSSR != true恫ćŖ悋恮恧
    // ctx.errorć«å…„ć£ć¦ć„ć‚‹å®Ÿč”Œę™‚ć®ć‚Øćƒ©ćƒ¼ęƒ…å ±ć‚’ä½æ恆
    if (nextData.props.pageProps.initialProps?.__errorInSSR) {
      // 恓恓悉ćøć‚“ć®ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ćÆnext-redux-wrapperćØ恋ä½æć£ć¦ć‚‹ćØå¤‰ć‚ć‚Šćć†ćŖć®ć§ć‚ˆć—ćŖć«ć—ć¦ćć ć•ć„
      return nextData.props.pageProps.initialProps;
    }

    return { statusCode: err?.statusCode ?? 500, message: err?.message };
  }

  if (res && err instanceof RouteError) {
    res.statusCode = err.statusCode;

    return {
      __errorInSSR: true,
      statusCode: err.statusCode,
      message: err.message,
    };
  }

  return {
    __errorInSSR: true,
    statusCode: 500,
  };
};

č§£čŖ¬

ErrorPage.getInitialProps恧ćÆē¬¬äø€å¼•ę•°ć®ctxć€ćć®ć†ć”ć®errćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ć«throw恕悌恟ć‚Øćƒ©ćƒ¼ć‚Ŗ惖ć‚ø悧ć‚Æ惈恌ęø”ć•ć‚Œć¦ćć¾ć™ć€‚ ć¤ć¾ć‚ŠNotFoundErrorć®ć‚¤ćƒ³ć‚¹ć‚æćƒ³ć‚¹ćŒę„ć¦ć„ć‚‹ćØć„ć†ęƒ³å®šć§ć™ć€‚

ć¾ćšćÆć‚·ćƒ³ćƒ—ćƒ«ć«err.statusCodeć‚’ć‚¹ćƒ†ćƒ¼ć‚æć‚¹ć‚³ćƒ¼ćƒ‰ć«čØ­å®šć—ć¦ćæć¾ć—ć‚‡ć†ć€‚

ErrorPage.getInitialProps = async (ctx): Promise<Props> => {
  const { res, err } = ctx

  // getInitialPropsćÆć‚Æćƒ©ć‚¤ć‚¢ćƒ³ćƒˆć‚µć‚¤ćƒ‰ć§ć‚‚å®Ÿč”Œć•ć‚Œć‚‹ć®ć§ć€res恌nullćŖ恓ćØ悂恂悋
  if (res && err instanceof RouteError) {
    // ć‚¢ćƒ—ćƒŖćŒę„å›³ēš„ć«ęŠ•ć’ćŸRouteErrorć‚Æćƒ©ć‚¹ć ć£ćŸć‚‰statusCode悒čØ­å®šć™ć‚‹
    res.statusCode = err.statusCode;
    return { statusCode: err.statusCode, message: err.message }
  }
  
  // RouteError恘悃ćŖć‹ć£ćŸć‚‰ć‚¬ćƒć®ć‚Øćƒ©ćƒ¼ć§ć‚ć‚‹ć§ć—ć‚‡ć†
  return { statusCode: 500,怀message: '[ć‚ćƒ¼ć—ćŒć‚¢ćƒ—ćƒŖć‚’å£Šć—ć¾ć—ćŸ]' }
}

ćŠć‚ć‚Šć§ć™ć€ć‚ˆć‹ć£ćŸć­ć€‚ 恝悓ćŖ悏恑ćŖ恄恠悍ā—ā—ā—ā—ā—

getInitialPropsćÆćƒžć‚¦ćƒ³ćƒˆę™‚ć«ć‚‚å®Ÿč”Œć•ć‚Œć‚‹ć€ä¾æåˆ©ć«ćƒ©ćƒƒćƒ—ć•ć‚ŒćŸerrćØäø€ē·’恫怂

ErrorPage.getInitialPropsćÆ态恩恆悄悉ć‚Æćƒ©ć‚¤ć‚¢ćƒ³ćƒˆć«ćƒžć‚¦ćƒ³ćƒˆć•ć‚ŒćŸéš›ć«ć‚‚ć†äø€åŗ¦å®Ÿč”Œć•ć‚Œć‚‹ć‚ˆć†ć§ć€
ćć®éš›ć«ctx.err恫ęø”ć£ć¦ćć‚‹ć‚Ŗ惖ć‚ø悧ć‚Æ惈ćÆšŸ‘‡ć®ć‚ˆć†ćŖNext.js恌ē‹¬č‡Ŗć«ćƒ©ćƒƒćƒ—ć—ćŸć‚Øćƒ©ćƒ¼ć‚Ŗ惖ć‚ø悧ć‚Æ惈恫ćŖć£ć¦ć—ć¾ć„ć¾ć™ć€‚

{ statusCode: 500, ...ćŖ悓恋悂恆1ćƒ—ćƒ­ćƒ‘ćƒ†ć‚£ćć‚‰ć„ }

statusCodeč‡Ŗ体ćÆ404恫ćŖć‚‹ć®ć§ć™ćŒć€ćƒ¦ćƒ¼ć‚¶ćƒ¼ć«åÆ¾ć™ć‚‹ē”»é¢äøŠć®ć‚Øćƒ©ćƒ¼č”Øē¤ŗ恌Internal Server Error恮ćØćć®ćƒ¤ćƒ„ć«ćŖć£ć¦ć—ć¾ć„ć¾ć™ć€‚čµ¦ć›ć­ć‡ć‚ˆć‚Ŗ悤

ćŖ恮恧态CSR恧悂恆äø€åŗ¦ćƒ¬ćƒ³ćƒ€ćƒŖćƒ³ć‚°ć•ć‚Œć‚‹éš›ćÆ态SSRꙂ恫čæ”ć•ć‚ŒćŸć‚ŖćƒŖć‚øćƒŠćƒ«ć®initialPropsć‚’å–å¾—ć™ć‚‹åæ…č¦ćŒć‚ć‚Šć¾ć™ć€‚ć—ć‹ć—ćć‚“ćŖ恓ćØ悒å‡ŗę„ć‚‹ę–¹ę³•ćÆNext恫ćÆē”Øę„ć•ć‚Œć¦ćŖć„ć®ć§ć€ęš“åŠ›ć‚’ęŒÆ悋恄仄äø‹ć®ć‚ˆć†ćŖ処ē†ć‚’čæ½åŠ ć—ć¾ć™ć€‚

  // CSRꙂ
  if (typeof window !== 'undefined') {
    // DOMäøŠć«ę®‹ć£ć¦ć„ć‚‹SSR恧hydrate恕悌恟ēŠ¶ę…‹ć‚’å–å¾—ć—ć¦ćć‚‹
    const nextData = window.__NEXT_DATA__;
    return nextData.props.pageProps;
  }

ć“ć‚Œć§ę™“ć‚Œć¦CSRꙂ恫悂SSRꙂćØ同ē­‰ć®ęƒ…å ±ć§ć€Œćć‚“ćŖćƒšćƒ¼ć‚øćÆćŖ恄悓恠悈怍ćØćƒ¦ćƒ¼ć‚¶ćƒ¼ć«ä¼ćˆć‚‰ć‚Œć¾ć™ć€‚

ćŠć‚ć‚Šć§ć™ć€ć‚ˆć‹ć£ćŸć­ć€‚ 恝悓ćŖ悏恑ćŖ恄恠悍ā—ā—ā—ā—ā—

ć‚Æćƒ©ć‚¤ć‚¢ćƒ³ćƒˆć‚µć‚¤ćƒ‰ć§ć®å®Ÿč”Œę™‚ć‚Øćƒ©ćƒ¼ć«åƾåæœć™ć‚‹

ćŖ悓ćØć“ć“ć¾ć§ć®åƾåæœć§ćÆć€ćƒ–ćƒ©ć‚¦ć‚¶äøŠć§å®Ÿč”Œę™‚ć‚Øćƒ©ćƒ¼ćŒē™ŗē”Ÿć—ćŸćØćć«ć‚‚ć€SSR恕悌恟Ꙃē‚¹ć®ć‚Øćƒ©ćƒ¼ēŠ¶ę…‹ćŒč”Øē¤ŗć•ć‚Œć¦ć—ć¾ć„ć¾ć™ć€‚äø€ē”ŸćŖļ¼ļ¼ļ¼ļ¼ ć‚Æćƒ©ć‚¤ć‚¢ćƒ³ćƒˆć‚µć‚¤ćƒ‰ć®å®Ÿč”Œę™‚ć‚Øćƒ©ćƒ¼ć«ć‚‚åƾåæœć—ć¾ć—ć‚‡ć†

先ēØ‹ć®ć‚³ćƒ¼ćƒ‰ć«åÆ¾ć—ć¦ć€šŸ‘‡ćæ恟恄ćŖ惎ćƒŖć®ć€ŒSSRꙂ恮ć‚Øćƒ©ćƒ¼ć§ćÆćŖ恄ꙂćÆꖰ恗恏ctx.errć«ę„ćŸć‚Øćƒ©ćƒ¼ć‚Ŗ惖ć‚ø悧ć‚Æ惈悒ä½æ恆怍ćØ恄恆åƾåæœć‚’å…„ć‚Œć¾ć™

 
  // CSRꙂ
  if (typeof window !== 'undefined') {
    const nextData = window.__NEXT_DATA__;
    const isSSRę™‚ć®åˆå›žć‚Øćƒ©ćƒ¼ = /* TODO */
    
    if (isSSRę™‚ć®åˆå›žć‚Øćƒ©ćƒ¼) {
      return nextData.props.pageProps;
    }
    
    return {
      statusCode: 500,
      message: `[恂ćŖćŸćŒć‚¢ćƒ—ćƒŖć‚’å£Šć—ćŸć‚“ć ļ¼ļ¼ļ¼] ${ctx.err.message}`
    }
  }

isSSRę™‚ć®åˆå›žć‚Øćƒ©ćƒ¼ć®åˆ¤å®šć‚’ć©ć†ć™ć‚‹ć‹ćØ恄恆ćØ恓悍恧恙恌态SSRꙂ恫ć‚Øćƒ©ćƒ¼ć‚’ćƒćƒ³ćƒ‰ćƒ«ć—ćŸę™‚ć«props.__errorInSSR = true ć«ć™ć‚‹ć‚ˆć†ć«ć—ć¾ć™ć€‚

  if (res && err instanceof RouteError) {
    res.statusCode = err.statusCode;

    return {
      __errorInSSR: true,
      statusCode: err.statusCode,
      message: err.message,
    };
  }

  return {
    __errorInSSR: true,
    statusCode: 500,
  };

恓恆恙悋恓ćØ恧äø€ē•Ŗęœ€åˆć«äøŠć’ćŸć‚³ćƒ¼ćƒ‰ć«ćŖ悊态SSR/CSRćØć‚‚ć«ć”ć‚ƒć‚“ćØć‚Øćƒ©ćƒ¼åƾåæœć§ćć¦ć€throw new NotFoundError()恙悋恠恑恧404恌čæ”恛悋ć‚Øćƒ©ćƒ¼ćƒšćƒ¼ć‚øćŒć§ćć¾ć™ć€‚

ćŠć‚ć‚Šć§ć™ć€ć‚ˆć‹ć£ćŸć­ć€‚
å¤§é›‘ęŠŠć«ä½œć£ćŸć‹ć‚‰ć€ē“°ć‹ć„å®Ÿč£…ćÆ恂ćØ恋悉悈恗ćŖ恫čŖ­ćæę›æćˆć¦ć­ć€‚<ę•¬å…· />

ćÆćŖ恏悉ēµ„代č”Ø čŠ±å€‰ę³•čÆč³¦é›… 悈悊

Discussion

ćƒ­ć‚°ć‚¤ćƒ³ć™ć‚‹ćØć‚³ćƒ”ćƒ³ćƒˆć§ćć¾ć™