Skip to content

Commit 3dbc7a1

Browse files
committed
Add og image
1 parent 6f9d254 commit 3dbc7a1

File tree

7 files changed

+442
-12
lines changed

7 files changed

+442
-12
lines changed

docusaurus.config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@ import remarkRawMarkdown from './src/plugins/remark-raw-markdown.mjs';
99
import darkTheme from './src/themes/react-navigation-dark';
1010
import lightTheme from './src/themes/react-navigation-light';
1111

12-
const url = 'https://reactnavigation.org';
1312
const latestVersion = '7.x';
1413

1514
const config: Config = {
1615
title: 'React Navigation',
1716
tagline: 'Routing and navigation for your React Native apps',
18-
url,
17+
url: process.env.URL || 'https://reactnavigation.org',
1918
baseUrl: '/',
2019
favicon: 'img/favicon.ico',
2120
organizationName: 'react-navigation',
@@ -134,7 +133,8 @@ const config: Config = {
134133
plugins: [
135134
'./src/plugins/disable-fully-specified.mjs',
136135
'./src/plugins/react-navigation-versions.mjs',
137-
['./src/plugins/llms-txt.mjs', { latestVersion, baseUrl: url }],
136+
['./src/plugins/llms-txt.mjs', { latestVersion }],
137+
'./src/plugins/og-image.ts',
138138
[
139139
'@docusaurus/plugin-client-redirects',
140140
{

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,22 @@
2424
"@docusaurus/remark-plugin-npm2yarn": "3.9.2",
2525
"@octokit/graphql": "^9.0.3",
2626
"@react-navigation/core": "^7.13.5",
27+
"@resvg/resvg-js": "^2.6.2",
2728
"dedent": "^1.7.0",
2829
"escape-html": "^1.0.3",
2930
"mkdirp": "^3.0.1",
3031
"netlify-plugin-cache": "^1.0.3",
3132
"prism-react-renderer": "^2.4.1",
3233
"react": "^19.2.1",
3334
"react-dom": "^19.2.1",
34-
"react-simple-code-editor": "^0.14.1"
35+
"react-simple-code-editor": "^0.14.1",
36+
"satori": "^0.19.2"
3537
},
3638
"devDependencies": {
3739
"@babel/types": "^7.28.5",
3840
"@docusaurus/tsconfig": "^3.9.2",
3941
"@ffprobe-installer/ffprobe": "^2.1.2",
42+
"@types/node": "^25.2.3",
4043
"@types/react": "^19.2.8",
4144
"@types/react-dom": "^19.2.3",
4245
"@voxpelli/node-test-pretty-reporter": "^1.1.2",

src/components/OgImage.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as React from 'react';
2+
3+
const h = React.createElement;
4+
5+
export default function OgImage({ logoBase64 }: { logoBase64: string }) {
6+
return h(
7+
'div',
8+
{ style: styles.container },
9+
h('img', { src: logoBase64, ...styles.logo })
10+
);
11+
}
12+
13+
const styles = {
14+
container: {
15+
background: '#1e2530',
16+
width: '100%',
17+
height: '100%',
18+
display: 'flex',
19+
alignItems: 'center',
20+
justifyContent: 'center',
21+
},
22+
logo: {
23+
width: 250,
24+
height: 250,
25+
},
26+
} as const;

src/plugins/llms-txt.mjs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -217,16 +217,13 @@ export default function (context, options) {
217217
return {
218218
name: 'llms.txt',
219219
async postBuild({ siteDir, outDir }) {
220-
const { latestVersion, baseUrl } = options;
220+
const { latestVersion } = options;
221+
const baseUrl = context.siteConfig.url;
221222

222223
if (!latestVersion) {
223224
throw new Error('[llms.txt] "latestVersion" option is required.');
224225
}
225226

226-
if (!baseUrl) {
227-
throw new Error('[llms.txt] "baseUrl" option is required.');
228-
}
229-
230227
const generatedFiles = [];
231228

232229
versions.forEach((version) => {

src/plugins/og-image.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import type { LoadContext, Plugin } from '@docusaurus/types';
2+
import { Resvg } from '@resvg/resvg-js';
3+
import fs from 'node:fs';
4+
import path from 'node:path';
5+
import util from 'node:util';
6+
import * as React from 'react';
7+
import satori from 'satori';
8+
9+
import OgImage from '../components/OgImage';
10+
11+
const WIDTH = 1200;
12+
const HEIGHT = 630;
13+
const OG_IMAGE_PATH = 'img/og-image.png';
14+
15+
function findHtmlFiles(dir: string): string[] {
16+
const results: string[] = [];
17+
18+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
19+
const fullPath = path.join(dir, entry.name);
20+
21+
if (entry.isDirectory()) {
22+
results.push(...findHtmlFiles(fullPath));
23+
} else if (entry.name === 'index.html') {
24+
results.push(fullPath);
25+
}
26+
}
27+
28+
return results;
29+
}
30+
31+
export default function ogImagePlugin(context: LoadContext): Plugin {
32+
return {
33+
name: 'og-image',
34+
35+
async postBuild({ outDir }) {
36+
const logoSvg = await fs.promises.readFile(
37+
path.join(context.siteDir, 'static/img/spiro_white.svg'),
38+
'utf-8'
39+
);
40+
41+
const logoBase64 = `data:image/svg+xml;base64,${Buffer.from(logoSvg).toString('base64')}`;
42+
43+
const svg = await satori(React.createElement(OgImage, { logoBase64 }), {
44+
width: WIDTH,
45+
height: HEIGHT,
46+
fonts: [],
47+
});
48+
49+
const resvg = new Resvg(svg, {
50+
fitTo: { mode: 'width', value: WIDTH },
51+
});
52+
53+
const png = new Uint8Array(resvg.render().asPng());
54+
const imgPath = path.join(outDir, OG_IMAGE_PATH);
55+
await fs.promises.writeFile(imgPath, png);
56+
57+
const ogUrl = `${context.siteConfig.url}/${OG_IMAGE_PATH}`;
58+
const htmlFiles = findHtmlFiles(outDir);
59+
60+
let injected = 0;
61+
62+
await Promise.all(
63+
htmlFiles.map(async (htmlFile) => {
64+
const html = await fs.promises.readFile(htmlFile, 'utf-8');
65+
66+
if (html.includes('og:image')) return;
67+
68+
const metaTags = [
69+
`<meta property="og:image" content="${ogUrl}">`,
70+
...(!html.includes('twitter:card')
71+
? [`<meta name="twitter:card" content="summary_large_image">`]
72+
: []),
73+
`<meta name="twitter:image" content="${ogUrl}">`,
74+
].join('\n ');
75+
76+
const updatedHtml = html.replace(
77+
'</head>',
78+
` ${metaTags}\n </head>`
79+
);
80+
await fs.promises.writeFile(htmlFile, updatedHtml);
81+
82+
injected++;
83+
})
84+
);
85+
86+
console.log(
87+
`${util.styleText(['magenta', 'bold'], '[og-image]')} Generated image for ${injected} pages`
88+
);
89+
},
90+
};
91+
}

tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
"extends": "@docusaurus/tsconfig",
33
"compilerOptions": {
44
"baseUrl": ".",
5+
"allowImportingTsExtensions": true,
6+
"rewriteRelativeImportExtensions": true,
57
"noEmit": true
68
},
7-
"include": ["src/**/*"],
9+
"include": ["src/**/*", "docusaurus.config.ts"],
810
"exclude": ["node_modules", "build", ".docusaurus"]
911
}

0 commit comments

Comments
 (0)