Next.js入門


Data Fetching (Pre-rendering)


  • ページがレンダリングされる前にデータをパッチする必要があります.

  • 既存のリアクターでは、ページが最初にロードされ(このときデータは通常[]が空)、useEffectの実行に伴い、中のデータキャプチャロジックが実行され、受信したデータがstateにロードされ、stateが変化するため、2回目のロードが行われる.

  • でもnextjsでレンダリングされたHTMLページは、2回目のレンダリングを待つことはありません.つまり、データの修正を待たない.この問題を解決するためにnext.jsのデータパッチ(getInitialiProps、getStaticProps、getStaticPath、getServerSideProps)を使用して、最初のレンダリングでデータがパッチされるようにデータを処理する必要があります.

  • すべてのページに共通のデータパッチが必要な場合、app.jsでデータを事前にパッチすればよい. ページごとに異なるデータが必要な場合は、ページごとにデータをパッチできます.
  • グローバルデータパッチ


    すべてのページに共通のデータパッチが必要な場合、つまりグローバルパッチが必要な場合はgetInitialPropsを使用する必要があります.
    // pages/_app.js
    
    import { NextPageContext } from 'next'
    
    function MyApp({ Component, pageProps }) {
      return (
        <>
          <AppLayout>
            <Component {...pageProps} />
          </AppLayout>
        </>
      );
    }
    
    MyApp.getInitialProps = async (context:NextPageContext) => {
      const { ctx, Component } = context;
      let pageProps = {};
    
      if (Component.getInitialProps) {
        pageProps = await Component.getInitialProps(ctx);
      }
    
      return { pageProps };
    };

    ページ単位のパッチデータ


    実際、getInitialPropsでデータをページ単位でパッチできます.ただし、静的データであるかページリクエストによるデータであるかによって、getStaticProps/getStaticPath/getServerSidePropsに分離します.
    Next.jsのプリレンダリングには2つの形式があります.
  • 状態生成:HTML生成時に生成され、生成されたHTMLは各リクエストで繰り返し使用される.getStaticProps***を使用します(必要に応じてgetStaticPathも使用できます).
  • サーバ-Side Rendering:要求ごとにHTMLを生成します.サーバ側のリストアは静的生成よりも遅いため、必要に応じてのみ使用することが望ましい.getServer SidePropsを使用します.
  • getStaticProps:静的データに使用され、構築中に実行されます.
    // pages/blog.js
    
    function Blog({ posts }) {
      return (
        <ul>
          {posts.map((post) => (
            <li>{post.title}</li>
          ))}
        </ul>
      )
    }
    
    export async function getStaticProps() {
      const res = await fetch('https://.../posts')
      const posts = await res.json()
    
      return {
        props: {
          posts,
        },
    		revalidate: 3600  //3600초마다 다시 빌드(데이터가 가끔 바뀔때 유용함)
      }
    }
    
    export default Blog
    getStaticPaths:動的ルーティング+getStaticProps
    -ページが動的ルーティングに書き込まれ、getStaticPropsに書き込まれている場合は、getStaticPathsを使用して構築時に静的にレンダリングするパスを設定する必要があります.
    // pages/posts/[id].js
    
    function Post({ post }) {
       return (
        <ul>
          {posts.map((post) => (
            <li>{post.title}</li>
          ))}
        </ul>
      )
    }
    
    export const getStaticPaths = async () => {
      const res = await fetch('https://.../posts')
      const posts = await res.json()
      const paths = posts.map((post) => ({
        params: { id: post.id },
      }))
      
      // { fallback: false } 는 다른 routes들은 404임을 의미
      // true이면 만들어지지 않은 것도 추후 요청이 들어오면 만들어 줄 거라는 뜻
      return { paths, fallback: false }
    }
    
    export const getStaticProps = async ({ params }) => {
      const res = await fetch(`https://.../posts/${params.id}`)
      const post = await res.json()
    
      return { props: { post } }
    }
    
    export default Post
    getServerSideProps:構築中は実行されず、各ページリクエストはサーバからデータを取得します.
    // pages/page.js
    
    function Page({ data }) {
      console.log(data)
    }
    
    
    export const getServerSideProps = async (context) => {
     
      const res = await fetch(`https://.../data`)
      const data = await res.json()
      
      return { props: { data: data } }
    }
    
    export default Page
    公式サイトでは、必要に応じてgetServer SidePropsのみを使用することを推奨しています.

    File-based Routing


  • Next.jsにはpagesフォルダの下のファイルに基づいて自動ルーティングを行う機能が内蔵されています.
  • 動的ルーティングの場合、[]を使用できます.
  • Fullstack Framework


    Next.jsにより、開発者はreactプロジェクトにバックエンドapiを簡単に追加できます.Reactプロジェクトには、データのロード、ストレージ、認証、認証プロセスを追加できます.(Node.js構文を使用)

    pagesフォルダの下にapiフォルダを追加し、apiパスに対応する名前のファイルを生成するだけです.
    // api/new-meetup.js
    
    import { MongoClient } from 'mongodb';
    
    async function handler(req, res) {
      if (req.method === 'POST') {
        const data = req.body;
    
        const client = await MongoClient.connect();
        const db = client.db();
        const meetupsCollection = db.collection('meetups');
    
        const result = await meetupsCollection.insertOne(data);
        console.log(result);
    
        client.close();
    
        res.status(201).json({ message: 'Meetup inserted!' });
      }
    }
    
    export default handler;
    以上のように、モンゴルdbストレージデータにアクセスするロジックを記述することができる.
    // new-meetup/index.js
    
    
    import { useRouter } from 'next/router';
    import NewMeetupForm from '../../components/meetups/NewMeetupForm';
    
    function NewMeetupPage() {
      const router = useRouter();
    
      const addMeetupHandler = async (enteredMeetupData) => {
        const response = await fetch('/api/new-meetup', {
          method: 'POST',
          body: JSON.stringify(enteredMeetupData),
          headers: {
            'Content-Type': 'application/json'
          }
        });
    
        const data = await response.json();
        console.log(data);
    
        router.push('/');
      }
    
      return (
        <NewMeetupForm onAddMeetup={addMeetupHandler} />
      )
    }
    
    export default NewMeetupPage;
    上記のように、fetch関数でapiフォルダの下のファイルパスを指定できます.

    その他


    Nexst.jsはImageラベルを提供します.次に、使用する画像ファイルのurlドメインです.config.jsに登録してから画像アドレスを使用できます.
    // next.config.js
    
    const nextConfig = {
      reactStrictMode: true,
      images: {
        domains: ['yt3.ggpht.com', 'i.ytimg.com']
      }
    }
    
    module.exports = nextConfig
    userouterを使用できます.
    // [newsId].js
    // our-domain.com/news/[newsId]
    
    import { useRouter } from 'next/router';
    
    function DetailPage() {
      const router = useRouter();
      console.log('router: ', router);
      const newsId = router.query.newsId;
    	console.log('newsId: ', newsId);
    
    	const onClinckHandler = () => {
    		router.push('/')
    	}
    
      return <button onClick={onClickHandler}>The Detail Page</button>
    }
    
    export default DetailPage;
    our-domain.com/news/123に入り、コンソールウィンドウは以下のようになります.

    context.params
  • getStaticProps/getServerSideProps関数では、UserRouterやUserQueryなどのhookは使用できません.
  • getStaticProps/getServerSideProps関数でデータパッチを行う場合、id値が必要になる場合があります.この場合、contextをパラメータとして受信し、paramsとして取得できます.
  • import MeetupDetail from '../../components/meetups/MeetupDetail';
    
    function MeetupDetails() {
      return (
        <MeetupDetail />
      )
    }
    
    export async function getStaticPaths() {
      return {
        fallback: false,
        paths: [
          {
            params: {
              meetupId: "m1",
            }
          },
          {
            params: {
              meetupId: "m2",
            }
          }
        ]
      }
    }
    
    export async function getStaticProps(context) {
      console.log('PARAMS: ', context.params);
    
      const meetupId = context.params.meetupId;
    
      return {
        props: {
          meetupData
        }
      }
    }
    
    export default MeetupDetails;
    Headタグを使用したメタデータの追加
    // pages/index.js
    
    import Head from 'next/head';
    import MeetupList from '../components/meetups/MeetupList';
    import { Fragment } from 'react';
    
    function HomePage(props) {
      return (
        <Fragment>
          <Head>
            <title>React Meetups</title>
    				<meta
              name='description'
              content='Browse a huge list of highly active React meetups!'
            />
          </Head>
          <MeetupList meetups={props.meetups} />
        </Fragment>
      )
    }
    各ページのインデックス.jsでは、タグに必要なコンテンツを追加できます.(SEOに良い)

    Next.jsでのApollo Graphqlの使用


    従来の方法:useQueryの使用
    import React from 'react'
    import Image from 'next/image';
    import VideoCard from './components/videoCard';
    import { useQuery, gql } from '@apollo/client';
    import styles from './channel.module.css';
    
    const GET_DATA = gql`
      query channel_channelInfo(
        $id: String
        $userId: String
      ) { 
        channel_channelInfo(
          id: $id
          userId: $userId
        ) { 
          title
          description
          banner
        }
      }
    `;
    
    export default function Channel() {
      const { error, loading, data} = useQuery(GET_DATA, {
    		variables: {
    			id: 'abcdefg', 
    			userId: 'Chaster'
    		}
    	})
    
      if(loading) return <p>Loading...</p>
      if(error) return <p>Error...</p>
    
      const channelData = data.channel_channelInfo;
    
      return (
        <div>
          <Image 
    		src={channelData.banner} 
    		alt='alt' 
    		height={270} 
    		width={1500}
           />
          <h2 className={styles.title}>{channelData.title}</h2>
          <p className={styles.desc}>{channelData.description}</p>
          <VideoCard videos={channelData.video}/>
        </div>
      )
    }
    Next.jsからApolloにデータを受信
  • Graphqlクライアントを個別のファイルに分離します.(既存のメソッドはapp.jsによって宣言されapolProviderに格納される)
  • // graphqlClient.js
    
    import { ApolloClient, InMemoryCache } from '@apollo/client';
    
    export const graphqlClient = new ApolloClient({
      uri: 'https://api.dev.vling.net/graphql',
      cache: new InMemoryCache()
    });
    import React from 'react'
    import Image from 'next/image';
    import VideoCard from './components/videoCard';
    import { gql } from '@apollo/client';
    import { graphqlClient } from './graphqlClient';
    import styles from './channel.module.css';
    
    const GET_DATA = gql`
      query channel_channelInfo(
        $id: String
        $userId: String
      ) { 
        channel_channelInfo(
          id: $id
          userId: $userId
        ) { 
          title
          description
          banner
        }
      }
    `;
    
    export default function Channel({ data }) {
      const channelData = data.channel_channelInfo;
    
      return (
        <div>
          <Image 
    		src={channelData.banner} 
    		alt='alt' 
    		height={270} 
    		width={1500} 		
          />
          <h2 className={styles.title}>{channelData.title}</h2>
          <p className={styles.desc}>{channelData.description}</p>
          <VideoCard videos={channelData.video} />
        </div>
      )
    }
    
    
    export const getStaticProps = async () => {
      const { data } = await graphqlClient.query({
        query: GET_DATA,
        variables: { 
    			id: 'abcdefg', 
    			userId: 'Chaster' 
    		}
      });
    
      return {
        props: {
          data
        }
      };
    }

    references


    https://nextjs.org/docs
    https://velog.io/@devstone/Next.js-100-活用-feat-initialProps-webpack-storybook