Skip to content

Commit 7172ace

Browse files
feat: nicer OG images for docs pages (#1443)
* feat: nicer OG images for docs pages * exclude docs pages from default og image * deduplicate font files --------- Co-authored-by: Chew Tee Ming <chew.tee.ming@nindatech.com>
1 parent d66890a commit 7172ace

File tree

7 files changed

+156
-3
lines changed

7 files changed

+156
-3
lines changed

apps/svelte.dev/src/routes/+layout.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
</script>
4040

4141
<svelte:head>
42-
{#if !page.route.id?.startsWith('/blog/')}
42+
{#if !page.route.id || !page.route.id.startsWith('/blog/') || !/^\/docs\/[^\/]+\/[^\/]+$/.test(page.route.id)}
4343
<meta name="twitter:card" content="summary" />
4444
<meta name="twitter:image" content="https://svelte.dev/images/twitter-thumbnail.jpg" />
4545
<meta name="og:image" content="https://svelte.dev/images/twitter-thumbnail.jpg" />

apps/svelte.dev/src/routes/blog/[slug]/card.png/+server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { read } from '$app/server';
55
import satori from 'satori';
66
import { html as toReactNode } from 'satori-html';
77
import Card from './Card.svelte';
8-
import DMSerifDisplay from './DMSerifDisplay-Regular.ttf?url';
9-
import FiraSans from './FiraSans-Regular.ttf?url';
8+
import DMSerifDisplay from '$lib/fonts/DMSerifDisplay-Regular.ttf?url';
9+
import FiraSans from '$lib/fonts/FiraSans-Regular.ttf?url';
1010
import { blog_posts } from '$lib/server/content';
1111
import type { ServerlessConfig } from '@sveltejs/adapter-vercel';
1212

apps/svelte.dev/src/routes/docs/[topic]/[...path]/+page.svelte

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import PageControls from '$lib/components/PageControls.svelte';
99
import { goto } from '$app/navigation';
1010
import { escape_html } from '$lib/utils/escape';
11+
import { page } from '$app/state';
1112
1213
let { data } = $props();
1314
@@ -64,7 +65,16 @@
6465
name="twitter:description"
6566
content="{data.document.metadata.title} • Svelte documentation"
6667
/>
68+
<meta name="twitter:card" content="summary_large_image" />
6769
<meta name="Description" content="{data.document.metadata.title} • Svelte documentation" />
70+
<meta
71+
name="twitter:image"
72+
content="https://svelte.dev/docs/{page.params.topic}/{page.params.path}/card.png"
73+
/>
74+
<meta
75+
name="og:image"
76+
content="https://svelte.dev/docs/{page.params.topic}/{page.params.path}/card.png"
77+
/>
6878
</svelte:head>
6979

7080
<div id="docs-content" use:legacy_details>
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { render } from 'svelte/server';
2+
import { Resvg } from '@resvg/resvg-js';
3+
import { error } from '@sveltejs/kit';
4+
import { read } from '$app/server';
5+
import satori from 'satori';
6+
import { html as toReactNode } from 'satori-html';
7+
import Card from './Card.svelte';
8+
import DMSerifDisplay from '$lib/fonts/DMSerifDisplay-Regular.ttf?url';
9+
import FiraSans from '$lib/fonts/FiraSans-Regular.ttf?url';
10+
import { docs } from '$lib/server/content';
11+
import type { ServerlessConfig } from '@sveltejs/adapter-vercel';
12+
13+
export const config: ServerlessConfig = {
14+
isr: {
15+
expiration: false
16+
}
17+
};
18+
19+
export function entries() {
20+
return Object.keys(docs.pages).map((doc) => {
21+
const full = doc.slice(5); // removes 'docs/' prefix
22+
const [topic, ...path] = full.split('/');
23+
return {
24+
topic,
25+
path: path.join('/')
26+
};
27+
});
28+
}
29+
30+
const height = 630;
31+
const width = 1200;
32+
const dm_serif_display = await read(DMSerifDisplay).arrayBuffer();
33+
const fira_sans = await read(FiraSans).arrayBuffer();
34+
35+
export async function GET({ params }) {
36+
const document = docs.pages[`docs/${params.topic}/${params.path}`];
37+
38+
if (!document) error(404);
39+
40+
const result = render(Card, {
41+
props: { title: document.metadata.title, breadcrumbs: document.breadcrumbs.slice(1) }
42+
});
43+
const element = toReactNode(`<head>${result.head}</head>${result.body}`);
44+
45+
const svg = await satori(element, {
46+
fonts: [
47+
{
48+
name: 'DMSerif Display',
49+
data: dm_serif_display,
50+
style: 'normal',
51+
weight: 400
52+
},
53+
{
54+
name: 'Fira Sans',
55+
data: fira_sans,
56+
style: 'normal',
57+
weight: 400
58+
}
59+
],
60+
height,
61+
width
62+
});
63+
64+
const resvg = new Resvg(svg, {
65+
fitTo: {
66+
mode: 'width',
67+
value: width
68+
}
69+
});
70+
71+
const image = resvg.render();
72+
73+
return new Response(image.asPng(), {
74+
headers: {
75+
'content-type': 'image/png',
76+
'cache-control': 'public, max-age=600' // cache for 10 minutes
77+
}
78+
});
79+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<svelte:options css="injected" />
2+
3+
<script lang="ts">
4+
let { title, breadcrumbs }: { title: string; breadcrumbs: Array<{ title: string }> } = $props();
5+
</script>
6+
7+
<div class="card">
8+
<img src="https://sveltejs.github.io/assets/artwork/svelte-machine.png" alt="Svelte Machine" />
9+
10+
<div class="text">
11+
<span class="breadcrumbs">
12+
<!-- for some reason `{#each}` doesn't work with satori here -->
13+
Docs • {breadcrumbs.map(({ title }) => title).join('')}
14+
</span>
15+
<h1>{title}</h1>
16+
</div>
17+
</div>
18+
19+
<style>
20+
.card {
21+
display: flex;
22+
width: 100%;
23+
height: 100%;
24+
background: white;
25+
}
26+
27+
img {
28+
position: absolute;
29+
width: 125%;
30+
height: 100%;
31+
top: 5%;
32+
left: 0;
33+
object-fit: cover;
34+
}
35+
36+
.text {
37+
display: flex;
38+
position: absolute;
39+
left: 80px;
40+
width: 55%;
41+
height: 80%;
42+
display: flex;
43+
flex-direction: column;
44+
justify-content: center;
45+
}
46+
47+
h1 {
48+
font-size: 72px;
49+
margin: 0;
50+
color: #222;
51+
font-weight: 400;
52+
line-height: 80px;
53+
margin: 0 0 0.5em 0;
54+
font-family: 'DM Serif Display';
55+
}
56+
57+
.breadcrumbs {
58+
font-size: 32px;
59+
margin: 0;
60+
color: #555;
61+
text-transform: uppercase;
62+
font-family: 'Fira Sans';
63+
}
64+
</style>

0 commit comments

Comments
 (0)