【Next.js】日付カードコンポーネントとHydrationWarning
完成物
上のようなものを作ります。当ブログにもある年・月・日を表示します。
作成
イメージ
イメージは「日めくりカレンダー」を意識しています。日付を横書きで記すだけでも良いと思いましたが、アクセントを付けたいということから始まりました。
コンポーネンの作成
執筆時のソースコードを用います。
まずは全文から。
import { mplus2 } from '@/lib/font';
export default function DateCard({ date }: { date?: string }) {
const formattedDate = date ? new Date(date.replace(/-/g, '/')) : undefined;
return (
<time className={mplus2.className} dateTime={formattedDate?.toISOString()}>
<div title={formattedDate?.toISOString()} className='flex items-center'>
<span className='ml-2 w-6 translate-y-4 -rotate-90 text-sm leading-4'>
{formattedDate ? formattedDate.getFullYear() : ''}
</span>
<div className='mr-4 flex size-16 flex-col items-center justify-center rounded-xl bg-gray-100 transition-colors dark:bg-slate-700'>
{formattedDate ? (
<div className='text-center'>
<div className='text-sm leading-4'>{formattedDate.getMonth() + 1}</div>
<div className='text-2xl leading-6'>{formattedDate.getDate()}</div>
</div>
) : (
<span className='text-3xl'>🎉</span>
)}
</div>
</div>
</time>
);
}
フォントについて
まずはフォントから、M PLUS 2
を使っています!
import { M_PLUS_2 } from 'next/font/google';
export const mplus2 = M_PLUS_2({
subsets: ['latin'],
weight: '400',
});
このように、next/font/google
からフォントをインポートし、インスタンスを作成することで使用可能になりexport
で他のファイルで利用可能にします。
HTML/CSS 設計について
まずは、time
タグで括ります。ここで、datetime属性を付与しておくことでSEO効果が..?
渡す値はISOフォーマットで問題ないでしょう。
ここによると、以下の記載があります。
明確な日付を表示する: ページ上で目立つように日付を表示します。
HTML内で明確な記載があることに越したことはありません。
実際、このように出力されている状態は正確です。
次に、表示の設計です。大きなdiv
で2つの要素を並べます。1つは「年」を回転した状態で表示する。もう1つはカレンダー形式で表示する、です。
<span className='ml-2 w-6 translate-y-4 -rotate-90 text-sm leading-4'>
{formattedDate ? formattedDate.getFullYear() : ''}
</span>
ここで、leading-4
は1行あたりの高さを表しており、複数行ある場合の間の調整に使うオプションです。90度回転し、inline-block
として判定を持っているので直接横のカレンダーとの隙間に響きます。
<div className='mr-4 flex size-16 flex-col items-center justify-center rounded-xl bg-gray-100 transition-colors dark:bg-slate-700'>
{formattedDate ? (
<div className='text-center'>
<div className='text-sm leading-4'>{formattedDate.getMonth() + 1}</div>
<div className='text-2xl leading-6'>{formattedDate.getDate()}</div>
</div>
) : (
<span className='text-3xl'>🎉</span>
)}
</div>
まずは、formattedDate
の定義によって分岐します。日付が存在しない場合を考慮して、取り敢えず🎉を表示するようにしました。
日付を中央ぞろえで表示し、formattedDate.getMonth() + 1
のように、月は+1で表示します。そのままでは0~11
の出力になるので注意です。
あとは、leading
で表示間を調整して完成です!
HydrationWarning (Error)について
今回のブログ作成で個人的に一番遭遇したエラーです。サーバーサイドとクライアントサイドのレンダリングに差があると生じます。
今回の件では同じ引数でも面白いことが起こります。
とPCでは表示されて、iPhoneでは、
まさかの NaN 出力!
スマホでローカルサーバにアクセスしたとき驚きました。原因解決を図っていたところ、違う表記にすればPCと同様の結果が得られました。
それが今回の
const formattedDate = date ? new Date(date.replace(/-/g, '/')) : undefined;
ですね。-
を/
に置き換えただけです。つまり、-
が無ければ問題回避ができました。そのため、現状はこのまま運用しています。
良い修正案はあるか?
しかし、あまり美しい修正方法とは言えません。今回の発生原因はクライアントサイドでコンポーネントを呼び出すことで発生します。サーバーサイドレンダリングでは完成済みで提供してくれますからね。
つまり、
-
が無ければ問題回避ができました。
電話番号として認識されたりするのでしょうか。
<meta name="format-detection" content="telephone=no"/>
iOSの親切な設計が表示を変えてしまいエラーを起こすならこれで解決するかも...?
もしくは、inline-block
として確立してもらうために<span>
で挟めば解決するかも...?
また検証した際に執筆しようと思います。