サイドピーク、始めました

# はじめに

こんにちは。
本記事は[NewsPicks Advent Calendar 2024](https://qiita.com/advent-calendar/2024/newspicks)の19日目の記事です。
昨日は石井さんの[「Androidで簡単にCompose化したいときは既存のコードを活かし段階的に移行しよう」](https://tech.uzabase.com/entry/2024/12/18/031126)でした！

# サイドピークってなに？

まずはこちらの動画をご覧ください。動画の画面右から表示されているのがサイドピークです。

![サイドピーク.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2428711/99688f5f-d0fd-5dbb-7515-d52aebf48a05.gif)

一般的にリンクをクリックするとページ全体が遷移する動きを想像しますが、サイドピークは元のページを維持したまま画面右側に小さなページUIを表示します。
エンジニアユーザーが多めのサービスだと[Notion](https://www.notion.com/ja/releases/2022-07-20)の動きがイメージしやすいかもしれません。

操作の流れはリンククリックで画面右からサイドピークが開きます。閉じる時はバツボタン、枠外クリック、ブラウザバックのいずれかです。

UIの狙いとして、NewsPicksにおいては記事の一覧から内容を軽く見て次の記事をまた見る、というザッピングのような体験を提供することにあります。
※本機能は現在A/Bテスト中のため、ユーザーによっては表示されないことがあります。

# 実現の流れ

サイドピークのようなページ内に追加でコンテンツを表示しつつURLを変更する実装の助けとして、Next.jsからIntercepting Routesが提供されています。
NewsPicksは現在Next.js12、Pages Routerを利用しており、当時Next.js13アップデートとApp Routerへの置き換えまでの余力がなかったため、今回は採用せず泥臭く実装する方針になりました。

https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes

ざっくりした実装の流れは以下の通りです。

![スクリーンショット 2024-12-17 19.53.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2428711/5f42880a-ed88-cc66-cd15-055154db8c36.png)


## 通常のページ遷移をキャンセル

記事リンクのonClickにevent.preventDefault()で通常のページ遷移をキャンセルして後述のサイドピーク展開の処理を加えます。
モバイルでは画面サイズの都合からサイドピークのUIが適していないため、通常のページ遷移を提供しています。
また、「新しいタブで開く」ショートカットでの操作の場合もサイドピーク展開ではなく、新しいタブでページを開きます。これは気になる記事を新しいタブでたくさん開いてから一気に見る、というユーザー操作の流れを阻害しないためです。

## useContextにコンテンツ情報とスクロール位置を保存

サイドピークはあらゆるページで表示される可能性のある共通コンポーネントのため、中身のコンテンツはStoreで管理しています。
useContextに「サイドピーク内に表示するReactNode」と「スクロール位置」を保存します。
このタイミングでデータフェッチを行い、サイドピーク内に表示するコンテンツのReactNodeを保存、表示しています。
スクロール位置はサイドピークを閉じた時に直前の状態を復元するために保存します。ブラウザによってはよしなに復元してくれますが、環境によって異なることもあったので挙動を一定にできるようアプリケーション側で制御します。
サイドピークを閉じた時はContextの内容を一度クリアします。

https://ja.react.dev/reference/react/useContext

## router.pushのshallowオプションでURL変更

サイドピークを開いた時はrouter.pushのshallowオプションで元ページのURLをベースにURL Queryを追加します。
shallowオプションを利用しすることでクリック時にページ遷移を挟まないことと、ブラウザバックした時に元の画面に戻れることを両立しています。
サイドピークの表示条件としては「指定のURL Queryがある」かつ「サイドピーク内に表示するReactNodeが存在する」ことにしています。

https://nextjs.org/docs/pages/building-your-application/routing/linking-and-navigating#shallow-routing

## CSS Transitionでサイドピーク開閉のアニメーション設定

サイドピーク開閉のアニメーションはCSS Transitionというライブラリを利用して実現しています。
かなり簡単に動きの設定ができるのでおすすめです。

- enter 開始時
- enter-active  アニメーション中
- enter-done    終了時

という形式で指定できます。

```css
&.side-peek-enter {
  transform: translateX(100%);
}

&.side-peek-enter-active {
  transition: transform 0.3s;
  transform: translateX(0);
}
```

https://reactcommunity.org/react-transition-group/css-transition

## 細かい体験向上

コンテンツのデータフェッチ前にスケルトンを表示しながら先にサイドピークを開くことでサクサクした体験を向上させています。開くのが1秒早いだけで体感かなり差を感じます。
また、SEO指標の1つであるINP評価の視点でもメリットがあります。

![スクリーンショット 2024-12-18 15.25.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2428711/77e8f5fc-7289-cb44-1a00-ab4412444138.png)

https://web.dev/articles/inp?hl=ja

## SEO対策

元々あった記事ページのURLとサイドピークを開いた状態のURLが異なり、SEO評価が分散されてしまうのを避けたかったため、元々のページURLをcanonicalに設定して評価を集約しています。

![スクリーンショット 2024-12-18 15.40.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2428711/17402056-4485-c589-d1bd-4cdedcc1ccd8.png)

https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls?hl=ja

# おわりに

ここまで読んでいただきありがとうございました。
サイドピークは珍しいUIであり、細かい体験や操作性を詰めていくところが難しさでもあり、面白さでもありました。また、デザイナーやPdMとの連携も重要なプロジェクトだと感じました。
この記事が今後サイドピークを実装する方の一助になればと思います。

明日は森田さんが投稿予定です。そちらもぜひご覧ください！

https://qiita.com/advent-calendar/2024/newspicks