AstroでYouTube埋め込みを軽量化:rehypeプラグインで遅延読み込みを実装
問題:YouTube埋め込みが重い
YouTubeの公式埋め込みコードをそのまま使うと、ページ読み込み時に大量のリソースを取得する。動画を見るかどうかわからないのに、JavaScriptやサムネイル画像が先読みされてしまう。
さらに、固定サイズ(width="560" height="315")なのでスマホではみ出る。
<iframe width="560" height="315"
src="https://www.youtube-nocookie.com/embed/VIDEO_ID"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>
既存ライブラリの検討
Astro向けのYouTube埋め込みライブラリとして astro-embed がある。遅延読み込みにも対応している。
しかし、使い方がこう:
---
import { YouTube } from 'astro-embed';
---
<YouTube id="VIDEO_ID" />
これは MDXでしか使えない。.mdファイルに <YouTube id="..." /> と書いても動かない。
既存の記事を全部MDXに書き換えるのは現実的ではない。Markdownの書き方はそのままで、自動的に遅延読み込みに対応させたい。
解決策:rehypeプラグインで自動変換
Astroのmarkdown処理はrehype(HTML用のプラグインシステム)に対応している。rehypeプラグインを作れば、ビルド時にiframeを検出して遅延読み込み用の構造に変換できる。
動作フロー
- ビルド時:rehypeプラグインがYouTubeのiframeを検出
- 変換:サムネイル画像 + 再生ボタンのHTML構造に置換
- 表示時:軽量なサムネイルのみ表示(YouTubeのJS読み込みなし)
- クリック時:JavaScriptでiframeを動的生成、autoplayで即再生
実装
1. 依存パッケージをインストール
pnpm add unist-util-visit rehype-raw
pnpm add -D @types/hast
unist-util-visit:AST(抽象構文木)を走査するユーティリティrehype-raw:Markdown内の生HTMLをASTとして処理するために必要
2. rehypeプラグインを作成
import type { Root, Element } from 'hast';
import { visit } from 'unist-util-visit';
export function rehypeYouTubeEmbed() {
return (tree: Root) => {
visit(tree, 'element', (node: Element) => {
if (node.tagName !== 'iframe') return;
const src = node.properties?.src as string | undefined;
if (!src) return;
// YouTube URLからビデオIDを抽出
const match = src.match(
/(?:youtube\.com|youtube-nocookie\.com)\/embed\/([a-zA-Z0-9_-]+)/
);
if (!match) return;
const videoId = match[1];
// 遅延読み込み用の構造に変換
node.tagName = 'div';
node.properties = {
className: ['youtube-embed'],
'data-video-id': videoId,
};
node.children = [
{
type: 'element',
tagName: 'img',
properties: {
src: `https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`,
alt: 'YouTube動画サムネイル',
loading: 'lazy',
},
children: [],
},
{
type: 'element',
tagName: 'button',
properties: {
className: ['youtube-play-button'],
'aria-label': '動画を再生',
},
children: [],
},
];
});
};
}
YouTubeのサムネイル画像は https://i.ytimg.com/vi/{VIDEO_ID}/maxresdefault.jpg で取得できる。
3. Astro設定にプラグインを追加
import { rehypeYouTubeEmbed } from './src/lib/rehype-youtube-embed';
import rehypeRaw from 'rehype-raw';
export default defineConfig({
markdown: {
rehypePlugins: [rehypeRaw, rehypeYouTubeEmbed],
// ... 他の設定
},
});
rehype-raw を先に実行することで、Markdown内の生HTMLがASTに変換され、その後 rehypeYouTubeEmbed で処理できるようになる。
4. CSSでスタイリング
.youtube-embed {
position: relative;
width: 100%;
max-width: 560px;
aspect-ratio: 16 / 9;
margin: 1.5rem 0;
border-radius: 0.5rem;
overflow: hidden;
cursor: pointer;
background: var(--secondary);
}
.youtube-embed img {
width: 100%;
height: 100%;
object-fit: cover;
transition: opacity 0.2s;
}
.youtube-embed:hover img {
opacity: 0.8;
}
.youtube-play-button {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 68px;
height: 48px;
background: rgba(255, 0, 0, 0.9);
border: none;
border-radius: 12px;
cursor: pointer;
}
.youtube-play-button::before {
content: '';
border-style: solid;
border-width: 10px 0 10px 18px;
border-color: transparent transparent transparent white;
margin-left: 4px;
}
.youtube-embed iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
aspect-ratio: 16 / 9 でレスポンシブ対応。スマホでもはみ出さない。
5. クリック時のJavaScript
function initYouTubeEmbeds() {
const embeds = document.querySelectorAll('.youtube-embed');
embeds.forEach((embed) => {
if (embed.querySelector('iframe')) return;
if (embed.dataset.initialized) return;
embed.dataset.initialized = 'true';
embed.addEventListener('click', () => {
const videoId = embed.getAttribute('data-video-id');
if (!videoId) return;
const iframe = document.createElement('iframe');
iframe.src = `https://www.youtube-nocookie.com/embed/${videoId}?autoplay=1`;
iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share';
iframe.allowFullscreen = true;
iframe.title = 'YouTube video player';
embed.innerHTML = '';
embed.appendChild(iframe);
});
});
}
initYouTubeEmbeds();
document.addEventListener('astro:page-load', initYouTubeEmbeds);
クリックすると autoplay=1 付きでiframeを生成するので、すぐに再生が始まる。
youtube-nocookie.com ドメインを使うことで、埋め込み時のトラッキングCookieを抑制できる。
結果
- 既存の記事を書き換える必要なし:iframeをそのまま書けばOK
- 初期読み込みが軽量化:YouTubeのJSを読み込まず、サムネイル画像のみ
- レスポンシブ対応:
aspect-ratioでスマホでもきれいに表示 - プライバシー配慮:
youtube-nocookie.comを使用
Markdownの書き方を変えずに、ビルド時の処理だけで最適化できるのがrehypeプラグインの強み。