📦

Nextjs14初心者入門-Loading UIとStreaming

2024/02/16に公開

まず初めに

最近、Nextjs14の初心者本を書きました。そこでは書ききれなかった内容がまだまだあるので、強化拡張パックとして記事に落とし込むことにしました。

もしまだ下記の本を読んでいない方は、ぜひこちらから読んでみてください。
https://zenn.dev/y_ta/books/eec3b78567aeeb

本を読んでいなくてもLoading UIとStreamingについてわかるようには書いています。

LoadingUI

appディレクトリの直下にloading.tsxを作成しましょう。

src
└ app
  └ loading.tsx
loading.tsx
const Loading = () => {
    return(
        <div className="flex justify-center items-center gap-6 mt-10">
            <div className="h-10 w-10 animate-spin border-[5px] border-sky-400 rounded-full  border-t-transparent"></div>
            <p className="text-[30px] font-weight">Loading</p>
        </div>
    )
}

export default Loading

また、blogフォルダの中のpage.tsxの中身をコンポーネント化しましょう。
waitTimeというpropsを受け取り、ローディング画面が見えやすいようにデータフェッチの時間を調整できるようにします。

src
└ app
  ├ blog
  │ └ page.tsx
  ├ components
  │ └ blogList.tsx
  └ loading.tsx
/app/components/blogList.tsx
import Loading from "@/app/loading";
import Link from "next/link"
import { Suspense } from "react";

interface TBlog {
    id: string;
    title: string;
    content: string;
}

const getBlogData = async (waitTime: number) => {

    //ルーディング画面がわかりやすくするために処理
    await new Promise(resolve => setTimeout(resolve, waitTime))

    const res = await fetch('http://localhost:3000/api/blog', {
        cache: 'no-store'
    })

    const blogData = await res.json()

    return blogData
}

const BlogList = async ({ waitTime }: { waitTime: number }) => {

    const blogData = await getBlogData(waitTime)

    return (
        <div className="grid grid-cols-12 gap-3 mb-3">
            {blogData.map((blog: TBlog) => (
                <div className="col-span-4 border border-black rounded p-5" key={blog.id}>
                    <Link href={`/blog/${blog.id}`} className="w-full">
                        <h2>{blog.title}</h2>
                    </Link>
                </div>
            ))}
        </div>
    )
}

export default BlogList
/app/blog/page.tsx
import BlogList from "@/app/components/blogList";
import Loading from "@/app/loading";

const BlogPage = async () => {

    return (
        <div className="container mx-auto py-[50px]">
            <h2 className="text-[50px] font-bold mb-5">Blog</h2>
            <BlogList waitTime={3000} />
        </div>
    )
}

export default BlogPage

/blogでページをリフレッシュしましょう。

すると先ほど作成したローディング画面が表示されます。
loading.tsxはNextjsにおいて特別な意味を持つファイルで、データフェッチなどの際にローディング画面を差し込むことができます。

Streaming

pattern1

blogListコンポーネントをSuspenseコンポーネントでラップし、fallbackにLoadingコンポーネントを渡しておきましょう。

/app/blog/page.tsx
import BlogList from "@/app/components/blogList";
import Loading from "@/app/loading";
import { Suspense } from "react";

const BlogPage = async () => {

    return (
        <div className="container mx-auto py-[50px]">
            <h2 className="text-[50px] font-bold mb-5">Blog</h2>
            <Suspense fallback={<Loading />}>
                <BlogList waitTime={3000} />
            </Suspense>
        </div>
    )
}

export default BlogPage

/blogでリフレッシュしましょう。

Suspenseコンポーネントでラップすることによって、ラップされたコンポーネントの部分がデータフェッチをすることを待たずして、他のUI部分を表示することができ、結果的にユーザー体験を向上させることができます。

pattern2

次にBlogListコンポーネントを複製し、複製した方のwaitTimeを5000としましょう。

/app/blog/page.tsx
import BlogList from "@/app/components/blogList";
import Loading from "@/app/loading";
import { Suspense } from "react";

const BlogPage = async () => {

    return (
        <div className="container mx-auto py-[50px]">
            <h2 className="text-[50px] font-bold mb-5">Blog</h2>
            <BlogList waitTime={3000} />
            <BlogList waitTime={5000} />
        </div>
    )
}

export default BlogPage

/blogでページをリフレッシュしましょう。

Suspenseでラップしないと、全てのデータフェッチが完了してからUIが表示されます。

次に各BlogListコンポーネントをSuspenseコンポーネントでラップして、fallbackにLoadingコンポーネントを渡しましょう。

/app/blog/page.tsx
import BlogList from "@/app/components/blogList";
import Loading from "@/app/loading";
import { Suspense } from "react";

const BlogPage = async () => {

    return (
        <div className="container mx-auto py-[50px]">
            <h2 className="text-[50px] font-bold mb-5">Blog</h2>
            <Suspense fallback={<Loading />}>
                <BlogList waitTime={3000} />
            </Suspense>
            <Suspense fallback={<Loading />}>
                <BlogList waitTime={5000} />
            </Suspense>
        </div>
    )
}

export default BlogPage

/blogでページをリフレッシュしましょう。

データフェッチが完了したUIから表示されます。

まとめ

他にも本には書ききれなかった内容がたくさんあるので、強化拡張パックという形で書いていきたいと思います。ここまで読んでいただきありがとうございました。

普段は、Reactを中心にWebのフロントエンドに関する発信をしています。他にも下記の本を書いているので興味があれば、ぜひ読んでみてください。

https://zenn.dev/y_ta/books/62a676a1d22982

https://zenn.dev/y_ta/books/a6ecee66233b20

https://zenn.dev/y_ta/books/d007090d6478dc

参考資料

https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming

Discussion