Skip to content

Commit b4242c1

Browse files
committed
feat: link preview
1 parent 050077b commit b4242c1

13 files changed

+348
-32
lines changed

content/astrodb-catchup.mdx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ tags: ['Astro', 'catchup']
77

88
## Astro DBとは
99

10-
[https://astro.build/db/](https://astro.build/db/)
11-
12-
ドキュメント [https://docs.astro.build/ja/guides/astro-db/](https://docs.astro.build/ja/guides/astro-db/)
10+
<LinkPreview url='https://docs.astro.build/ja/guides/astro-db/' />
1311

1412
> Astro DB is a fully-managed SQL database designed exclusively for Astro.
1513

content/first-blog.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ date: 2024-02-07
55
tags: ['Next.js', 'blog']
66
---
77

8-
[https://fumadocs.vercel.app/](https://fumadocs.vercel.app/)
8+
<LinkPreview url='https://fumadocs.vercel.app/' />
99

1010
このFumadocsを使ってブログサイトを立ち上げました。
1111

content/getting-started-in-cyber-security.mdx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,7 @@ tags: ['Linux', 'WSL', 'security']
1111

1212
チャンネルはこちらです。
1313

14-
<Card
15-
href="https://www.youtube.com/@tamashiiwoyokose"
16-
title="しいたま@サイバーセキュリティ"
17-
description="free cybersecurity education and ethical hacking このチャンネルでは、主にサイバーセキュリティの話題に関する解説動画を投稿します。 動画内の技術は、法律の範囲内でのみ使用し、悪用はしないでください"
18-
/>
14+
<LinkPreview url='https://www.youtube.com/@tamashiiwoyokose'/>
1915

2016
動画内でKali LinuxというOSと[TryHackMe](https://tryhackme.com/)というWebサイトでハッキングをしていて、とりあえず自分も真似してみたいと思います。
2117

content/next-stack.mdx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ Cloudflare Workersにデプロイするとしても、要件によってはSupab
2222

2323
この記事を読むにあたって、今回の構成のサンプルプロジェクトがあるので実際のコードと照らし合わせながら見るとわかりやすいと思います。
2424

25-
Webサイト: [https://next-stack.runfunrun.workers.dev](https://next-stack.runfunrun.workers.dev/)
25+
- Webサイト
26+
[https://next-stack.runfunrun.workers.dev](https://next-stack.runfunrun.workers.dev/)
2627

27-
リポジトリ: [https://github.com/RUNFUNRUN/next-stack](https://github.com/RUNFUNRUN/next-stack)
28+
- リポジトリ
29+
<LinkPreview url='https://github.com/RUNFUNRUN/next-stack'/>
2830

2931
<Callout title='初心者の方へ'>
3032
この記事ではかなり簡略化して説明しています。
@@ -35,7 +37,7 @@ Webサイト: [https://next-stack.runfunrun.workers.dev](https://next-stack.runf
3537

3638
## PaaS
3739

38-
[Cloudflare Workers · Cloudflare Workers docs](https://developers.cloudflare.com/workers/)
40+
<LinkPreview url='https://developers.cloudflare.com/workers/'/>
3941

4042
冒頭でも紹介した通り、Cloudflare Workersを使用します。
4143

@@ -44,7 +46,7 @@ Webサイト: [https://next-stack.runfunrun.workers.dev](https://next-stack.runf
4446

4547
## フレームワーク
4648

47-
[Next.js by Vercel - The React Framework](https://nextjs.org/)
49+
<LinkPreview url='https://nextjs.org/'/>
4850

4951
タイトルにある通り、Next.jsを使います。バックエンドも含めたフルスタックな使い方をする前提です。
5052

@@ -65,7 +67,7 @@ next-on-pagesで言う`getRequestContext`相当になります。
6567

6668
### バックエンド
6769

68-
[Hono - Web framework built on Web Standards](https://hono.dev/)
70+
<LinkPreview url='https://hono.dev/'/>
6971

7072
Next.jsのフルスタック構成ですが、API RoutesでHonoを使用します。
7173

@@ -118,7 +120,7 @@ Hono RPCの詳細は[ドキュメント](https://hono.dev/docs/guides/rpc)をご
118120

119121
## ORM
120122

121-
[Drizzle ORM - next gen TypeScript ORM.](https://orm.drizzle.team/)
123+
<LinkPreview url='https://orm.drizzle.team/'/>
122124

123125
今回はDrizzleを使用します。容量が小さいのでWorkersと相性が良いです。
124126

@@ -136,7 +138,7 @@ export default defineConfig({
136138

137139
## DB
138140

139-
[Cloudflare D1 · Cloudflare D1 docs](https://developers.cloudflare.com/d1/)
141+
<LinkPreview url='https://developers.cloudflare.com/d1/'/>
140142

141143
こちらも冒頭でも話しましたが、Cloudflare D1を使用します。
142144

@@ -154,7 +156,7 @@ bunx wrangler d1 execute next-stack --file=./drizzle/migrations/0000_calm_shocke
154156

155157
## 認証
156158

157-
[Better Auth](https://www.better-auth.com/)
159+
<LinkPreview url='https://www.better-auth.com/'/>
158160

159161
個人的に今TypeScriptで一番アツい認証ライブラリです。
160162

@@ -256,7 +258,11 @@ export const authMiddleware = createMiddleware<{
256258
実際のコード例を出しつつ紹介しました。
257259
サンプルプロジェクトに全てコードはあるので、そちらを見てもらうとわかりやすいとは思います。
258260

259-
また、決済に関してはOpenNextのドキュメントに[Stripeについての記述](https://opennext.js.org/cloudflare/howtos/stripeAPI)があるので、そちらをご覧ください。
261+
また、決済に関してはOpenNextやBetter AuthドキュメントにStripeについての記述があるので、そちらをご覧ください。
262+
263+
<LinkPreview url='https://www.better-auth.com/docs/plugins/stripe' />
264+
265+
<LinkPreview url='https://opennext.js.org/cloudflare/howtos/stripeAPI' />
260266

261267
もしよろしければ、この構成を試してみてください。
262268

content/nvim-funny-plugins.mdx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ tags: ['Neovim']
1010
今回は全く必要ないけど、あるとちょっと楽しいプラグインを3つ紹介したいと思います。
1111
以下の3つなります。
1212

13-
- [andweeb/presence.nvim](https://github.com/andweeb/presence.nvim)
13+
- andweeb/presence.nvim
1414

15-
- [sphamba/smear-cursor.nvim](https://github.com/sphamba/smear-cursor.nvim)
15+
<LinkPreview url='https://github.com/andweeb/presence.nvim'/>
1616

17-
- [nvzone/showkeys](https://github.com/nvzone/showkeys)
17+
- sphamba/smear-cursor.nvim
18+
19+
<LinkPreview url='https://github.com/sphamba/smear-cursor.nvim'/>
20+
21+
- nvzone/showkeys
22+
23+
<LinkPreview url='https://github.com/nvzone/showkeys'/>
1824

1925
設定はLazyVimの場合を書いています。
2026
皆さんが使ってる各種プラグインマネージャー・ディストリビューションに合わせて読み替えてください。

content/nvim-japanese-win.mdx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@ tags: ['Neovim', 'Windows', 'WSL']
99

1010
前回の記事の続きになります。読んでない方はぜひお読みください。
1111

12-
<Card
13-
title='Neovimオススメ設定② 日本語入力を快適にするim-select.nvimのすすめ'
14-
description='Neovimのオススメ設定シリーズ第2弾。'
15-
href='/posts/nvim-japanese'
16-
/>
12+
<LinkPreview url='/posts/nvim-japanese'/>
1713

1814
Macではかなり理想な状態でした。
1915
この記事でわかるように、WindowsまたはWSLでの日本語入力には大きな課題があります。

content/nvim-japanese.mdx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,7 @@ tags: ['Neovim']
1616

1717
<Callout title='WindowsまたはWSLの方はこちらもご覧ください。'>
1818
※ 追記
19-
<Card
20-
title='Neovimオススメ設定③ Windows or WSLでも快適に日本語入力がしたい'
21-
description='Neovimのオススメ設定シリーズ第3弾。'
22-
href='/posts/nvim-japanese-win'
23-
/>
19+
<LinkPreview url='/posts/nvim-japanese-win'/>
2420
</Callout>
2521

2622
## im-select.nvim

content/tailscale-ssh.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ tags: ['Tailscale', 'security', 'catchup']
77

88
## tailscaleについて
99

10-
[https://tailscale.com/](https://tailscale.com/)
10+
<LinkPreview url='https://tailscale.com/' />
1111

1212
![about](https://i.gyazo.com/a5f4ec3808b421f3c189b7f5f9c2675c.png)
1313

next.config.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ const config = {
1010
protocol: 'https',
1111
hostname: 'i.gyazo.com',
1212
},
13+
{
14+
protocol: 'https',
15+
hostname: 't1.gstatic.com',
16+
},
1317
],
1418
},
1519
reactStrictMode: true,

src/actions/fetch-og-metadata.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use server';
2+
3+
import { cache } from 'react';
4+
5+
type OGData = {
6+
title: string;
7+
description: string;
8+
image: string;
9+
url: string;
10+
};
11+
12+
export const getOGData = cache(
13+
async (url: string): Promise<Partial<OGData>> => {
14+
try {
15+
const response = await fetch(url, {
16+
headers: {
17+
'User-Agent': 'RUNFUNRUN.dev',
18+
},
19+
});
20+
21+
const html = await response.text();
22+
23+
const getMetaContent = (property: string): string | undefined => {
24+
const regex = new RegExp(
25+
`<meta[^>]+(?:property|name)="${property}"[^>]+content="([^"]+)"`,
26+
'i',
27+
);
28+
return regex.exec(html)?.[1].replaceAll('amp;', '');
29+
};
30+
31+
const titleMatch = /<title>(.*?)<\/title>/i.exec(html);
32+
33+
return {
34+
title: getMetaContent('og:title') || titleMatch?.[1] || '',
35+
description:
36+
getMetaContent('og:description') ||
37+
getMetaContent('description') ||
38+
'',
39+
image: getMetaContent('og:image') || '',
40+
url,
41+
};
42+
} catch (error) {
43+
return { url };
44+
}
45+
},
46+
);

src/app/posts/[slug]/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { PostJsonLd } from '@/components/json-ld';
2+
import { LinkPreview } from '@/components/link-preview';
23
import { TagCard } from '@/components/tag-card';
34
import { getPost, getPosts } from '@/lib/source';
45
import { CodeBlock, Pre } from 'fumadocs-ui/components/codeblock';
@@ -79,6 +80,7 @@ const Page = async (props: { params: Promise<{ slug: string }> }) => {
7980
<Pre>{props.children}</Pre>
8081
</CodeBlock>
8182
),
83+
LinkPreview,
8284
}}
8385
/>
8486
</DocsBody>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use client';
2+
3+
import { cn } from 'fumadocs-ui/components/api';
4+
import Image from 'next/image';
5+
import { useState } from 'react';
6+
7+
export const ImageWithFallback = ({
8+
src,
9+
alt,
10+
className,
11+
}: {
12+
src?: string;
13+
alt: string;
14+
className?: string;
15+
}) => {
16+
const [error, setError] = useState(false);
17+
18+
if (!src || error) {
19+
return (
20+
<div className='flex h-full w-full items-center justify-center bg-fd-card'>
21+
<span className='text-4xl text-fd-card-foreground'>🔗</span>
22+
</div>
23+
);
24+
}
25+
26+
return (
27+
<Image
28+
src={src}
29+
alt={alt}
30+
className={cn('object-cover', className)}
31+
fill
32+
sizes='240px'
33+
onError={() => setError(true)}
34+
unoptimized={src.startsWith('http')}
35+
/>
36+
);
37+
};

0 commit comments

Comments
 (0)