🗂
Next.jsで作ったブログにリンクカードを実装する
リンクカードとは?
これです。外部リンクをMarkdownに記載したときに、自動的にリンク先の<meta>
からtitle
, description
, image
の情報を取ってきて、カード形式でリンク先の情報を表示するものです。
↓こんな感じのものです。
References
こちらの記事を参考に実装しました。
環境
- Next.js
- Tailwind(css)
コード
/pages/Post.tsx
記事を表示するページです。SSGにしているので、getStaticProps
でmarkdownから、URLを正規表現で抽出し、getMetaData
でURLにアクセスして、<meta>
の情報を取得して、NextPageに渡しています。getMetaDataでは、JSDOMを使っています。
pages/Post.tsx
import Markdown from '@/hogehoge'
export type Meta = {
url: string
title: string
description: string
image: string
}
const Post: NextPage<PostProps> = ({ post, metas }) => {
...
return (
<div>
...
<Markdown source={post.content} metas={metas} />
</div>
)
}
async function getMetaData(url: string): Promise<Meta> {
const metaData = {
url,
title: '',
description: '',
image: '',
}
try {
const res = await fetch(url)
const text = await res.text()
const doms = new JSDOM(text)
const metas = doms.window.document.getElementsByTagName('meta')
for (const meta of metas) {
const np = meta.getAttribute('name') || meta.getAttribute('property')
if (typeof np !== 'string') continue
if (np.match(/title/)) {
metaData.title = meta.getAttribute('content')
}
if (np.match(/description/)) {
metaData.description = meta.getAttribute('content').slice(0, 100)
}
if (np.match(/image/)) {
metaData.image = meta.getAttribute('content')
}
}
} catch (e) {
console.error(e)
}
return metaData
}
function getUrlList(content: string): Array<string> {
return content.match(/https?:\/\/[^\n\]]*/g) ?? []
}
export const getStaticProps: GetStaticProps<PostProps, Params> = async ({ params }) => {
const post = getPostByPath() // mdが記載されているファイルを取得
const urls = getUrlList(post.content) // mdの中から、外部リンク(URL)を配列で取得
const metas = await Promise.all(urls.map(async (url) => await getMetaData(url))) // metaタグの情報を取得
const filteredMetas = metas.filter((m) => m !== undefined) // undefinedのものをfilter
return {
props: {
...
metas: filteredMetas, // propsを使って、Postに渡す
},
}
}
/components/Markdown.tsx
Markdownを表示するコンポーネントです。ReactMarkdownを使っており、以下のようにしています。
Markdown.tsx
...
import { Meta } from '@/pages/posts/[year]/[month]/[slug]'
export interface MarkdownInterface {
source: string
metas: Meta[]
}
const Markdown: React.FC<MarkdownInterface> = ({ source, metas }) => {
...
const a: Components['a'] = (props) => {
...
return (
<LinkCard href={href} metas={metas}>
{children}
</LinkCard>
)
}
return (
<div className="markdown">
<ReactMarkdown
components={{
...
a,
}}
>
{source}
</ReactMarkdown>
</div>
)
}
export default Markdown
/components/LinkCard.tsx
リンクカードのコンポーネントです。CSSは、tailwindを使っています。
LinkCard.tsx
import { Meta } from '@/hogehoge'
interface P {
href: string
children: any
metas: Meta[]
}
const LinkCard: React.FC<P> = ({ href, children, metas }) => {
const target = metas.find((meta) => meta.url == href)
if (target) {
return (
<a href={href} target="_blank" rel="noreferrer">
<div className="w-full flex justify-around bg-white rounded-md p-3 border lg:w-1/2">
<div className="w-1/2">
<img src={target.image} alt={target.title} className="max-h-20 m-auto" />
</div>
<div className="flex flex-col justify-start px-1 ml-3">
<div className="text-sm font-bold text-black whitespace-pre-wrap">{target.title}</div>
<div className="text-gray-400 text-xs whitespace-pre-wrap">{target.description}</div>
</div>
</div>
</a>
)
}
return (
<a href={href} target="_brank">
{children}
</a>
)
}
export default LinkCard
Discussion