Notionのアップロード画像の非表示をSSRで解決する方法

:::note info
本記事は僕の [NotionAPI+Next.jsコース](https://www.takux.one/#notion-next-blog) 用に書いていますが、受講されていなくても NotionとNext.jsを利用している方には参考にしていただけると思います。
:::

Notionへ独自にアップロードした画像には有効期限が設定されます。例えば下記のようにURLクエリに `X-Amz-Expires=3600` と記載されていて、1時間だけ有効なリンクになります。

```
https://s3.us-west-2.amazonaws.com/secure.notion-static.com/cef557fb-f7d0-425b-9f28-5b420f74b15a/sample.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220725%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220725T004702Z&X-Amz-Expires=3600&X-Amz-Signature=8c705ac0a311dec22fab6baa36424543cb8c99cf222f278f7c61bb05f04c66b6&X-Amz-SignedHeaders=host&x-id=GetObject
```
  



**SSGの場合** ⇒ 有効期限が切れている状態では画像が非表示になります。

**ISRの場合** ⇒ 有効期限が切れている状態では画像が非表示になります。ページをリロード（二度目のアクセス）すると表示されるようになります。

こちらの件について、SSR + ローディングアニメーションで対応する場合の記事になります。

## Notionがこのようにしている理由

[https://developers.notion.com/docs/working-with-files-and-media](https://developers.notion.com/docs/working-with-files-and-media)

https://twitter.com/NotionHQ/status/1463669460938760198?s=20&t=jn_IKGFCLfip5TjClBfy2A


## SSRに変更した方がいいケースとそうでないケース

作られたブログページへのアクセス数が多い場合は、ISRでも常に画像URLが更新されることによって画像の表示状態を保つことができます。

アクセス数が少ない場合は、非表示の機会も増えてしまいます。

これは作られたブログページの規模によるのでケースバイケースです。アクセス数が少ない場合は以下を参考にSSRへ変更してみるのもいいと思います。

## SSRについて改めて

SSRにして解決する方法を紹介します。まずSSRのメリット、デメリットは簡単に次のようになります。

**SSRのメリット**

- 必ず毎回最新の情報を取得するため、画像も常に表示される。

**SSRのデメリット**

- 毎回データをフェッチしてレンダリングするので読み込みが遅い。

メリットを享受しつつ、デメリットを低くすることを考えます。

読み込みが遅いとユーザーにとってページが固まっている状態が続くことになります。ページの離脱にもつながります。

この問題を、UX的にローディングアニメーションを追加することで改善させます。

:::note info
個人的には、今回に限らずローディングアニメーションはUXを損なわない、かなり効果がある方法だと考えています。「くるくる」回っているだけで何もない時に比べて読み込みを**待てる**のは不思議です。
:::

## 本記事以下で行うこと

以上より、本記事では以下を行います。

- SSG/ISRをSSRに変更。
- ローディングアニメーション用コンポーネントを作成
- ローディングイベント用の設定を追記
- イベントを一括検知するため_app.tsxに設定
- next.config.js へ s3 のドメインを追加

## SSG/ISRをSSRに変更

基本的には変更するページの getStaticProps を getServerSideProps へ変更するだけです。またrevalidate や getStaticPaths があれば削除します。

- 関数名を変更: getStaticProps ⇒ getServerSideProps
- 型定義も変更: GetStaticProps ⇒ GetServerSideProps
- revalidate がある場合はその行を削除
- getStaticPathsがある場合は削除

例えば index.tsx の getStaticProps を getServerSideProps へ変更すると下記のようになります。 



```tsx:pages/index.tsx
export const getServerSideProps: GetServerSideProps = async () => {
  const { results } = await fetchPages({});
  return {
    props: {
      pages: results ? results : [],
    },
  };
};
```

以上のようにそれぞれのページを編集します。

SSRへ変更後のコード:

- [pages/index.tsx](https://github.com/takux/notion-next-blog-course/blob/main/sec10/notion-next-blog/pages/index.tsx)
- [pages/articles/[slug].tsx](https://github.com/takux/notion-next-blog-course/blob/main/sec10/notion-next-blog/pages/articles/%5Bslug%5D.tsx)
- [pages/tags/[tag].tsx](https://github.com/takux/notion-next-blog-course/blob/main/sec10/notion-next-blog/pages/tags/%5Btag%5D.tsx)

## ローディングアニメーション用コンポーネントを作成

今回、ローディング中にはスピナーのアニメーションを表示したいと思います。これはデザイン的なところなので、ローディング中に表示したいものをご自由に設定してみてください。

componentsの中にSpinner.tsxを作成し下記を貼り付けます。

```tsx:components/Spinner.tsx
import { FC } from "react";

export const Spinner: FC = () => {
  return (
    <div className="bg-white fixed inset-0 z-40 flex h-screen w-screen place-items-center justify-center bg-base-100 ">
      <div className="z-50 h-12 w-12 animate-spin rounded-full border-4 border-blue-400 border-t-transparent opacity-100 "></div>
    </div>
  );
};
```

## ローディングイベント用の設定を追記

ページ遷移を検知し、自動的にアニメーションが発動するように設定します。

まずcomponentsの中にLoader.tsxを作成し下記を貼り付けます。

```tsx:components/Loader.tsx
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { Spinner } from "./Spinner";

const Loader = () => {
  const router = useRouter();
  const [loading, setLoading] = useState(false);
  useEffect(() => {
    const handleStart = (url: string) =>
      url !== router.asPath && setLoading(true);

    const handleComplete = () => setLoading(false);

    router.events.on("routeChangeStart", handleStart);
    router.events.on("routeChangeComplete", handleComplete);
    router.events.on("routeChangeError", handleComplete);
  }, [router.events, router.asPath]);

  return <>{loading && <Spinner />}</>;
};

export default Loader;
```

- ルート変更がスタートすると `routeChangeStart` イベントが発生します。
- ルート変更が完了すると `routeChangeComplete` イベントが発生します。
- ルート変更にエラーがあると `routeChangeError` イベントが発生します。

以上より、ルート変更のスタートでアニメーションを実行（表示）させ、完了またはエラーの場合にアニメーションを終了（非表示）させます。

router.eventsについて: 
[https://nextjs.org/docs/api-reference/next/router#routerevents](https://nextjs.org/docs/api-reference/next/router#routerevents)

## イベントを一括検知するため_app.tsxに設定

Loaderをアプリケーション全体に適用するため、_app.tsxへ追記します。追記した結果、現在の_app.tsxは下記のようになります。

```tsx:pages/_app.tsx
import "../styles/globals.css";
import type { AppProps } from "next/app";
import Loader from "../components/Loader";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <>
      <Loader />
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;
```

## next.config.jsへs3のドメインを追加

コースではNext.jsのImageコンポーネントを使用しています。

そのためNotionに画像をアップロードすると Next.js が画像ホストについてエラーが出ます。（既に設定が完了している場合は出ません）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2706663/1cc0c235-f6ae-1cfd-0f0f-e8fa1dd9d23e.png)

上記画像の hostname に表示されているドメインを next.config.js の domains へ追加しましょう。

:::note warn
ホスト名は皆さんそれぞれ表示されているものを追記してください。
:::

```jsx:next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  images: {
    domains: [
      "www.notion.so",
      "images.unsplash.com",
      "s3.us-west-2.amazonaws.com",  // 表示されているホスト名を追記
    ],
  },
};

module.exports = nextConfig;
```

以上で完了です。

改めてデプロイすれば本番環境でもうまくアニメーションが表示されていると思います。