Fork of Chiri for Astro for my blog
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(post): add og image support

the3ash 7ba3d215 3d2db4e6

+100 -5
+2 -2
README.md
··· 1 1 # Chiri 🌸 2 2 3 - ![screenshot-light](public/images/screenshot-light.png) 4 - ![screenshot-dark](public/images/screenshot-dark.png) 3 + ![screenshot-light](public/screenshots/screenshot-light.png) 4 + ![screenshot-dark](public/screenshots/screenshot-dark.png) 5 5 6 6 Chiri is a minimal blog theme built with [Astro](https://astro.build), offering customization options while preserving its clean aesthetic. 7 7
+2
package.json
··· 20 20 "@astrojs/sitemap": "^3.4.1", 21 21 "@playform/inline": "^0.1.2", 22 22 "astro": "^5.11.1", 23 + "astro-og-canvas": "^0.7.0", 24 + "canvaskit-wasm": "^0.40.0", 23 25 "katex": "^0.16.22", 24 26 "mdast-util-to-string": "^4.0.0", 25 27 "reading-time": "^1.5.0",
+38
pnpm-lock.yaml
··· 20 20 astro: 21 21 specifier: ^5.11.1 22 22 version: 5.11.1(@types/node@24.0.13)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.8.3) 23 + astro-og-canvas: 24 + specifier: ^0.7.0 25 + version: 0.7.0(astro@5.11.1(@types/node@24.0.13)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.8.3)) 26 + canvaskit-wasm: 27 + specifier: ^0.40.0 28 + version: 0.40.0 23 29 katex: 24 30 specifier: ^0.16.22 25 31 version: 0.16.22 ··· 851 857 '@ungap/structured-clone@1.3.0': 852 858 resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} 853 859 860 + '@webgpu/types@0.1.21': 861 + resolution: {integrity: sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==} 862 + 854 863 acorn-jsx@5.3.2: 855 864 resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 856 865 peerDependencies: ··· 908 917 resolution: {integrity: sha512-JepyLROIad6f44uyqMF6HKE2QbunNzp3mYKRcPoDGt0QkxXmH222FAFC64WTyQu2Kg8NNEXHTN/sWuUId9sSxw==} 909 918 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 910 919 920 + astro-og-canvas@0.7.0: 921 + resolution: {integrity: sha512-aamARDNDORPxXlPt+VJakzj4WpNUDjQqUZ06eE82pGMad4aTR/KMm8Ho12qvprgJcM8K7fgSOMg5OHa7KcX0pw==} 922 + engines: {node: '>=18.14.1'} 923 + peerDependencies: 924 + astro: ^3.0.0 || ^4.0.0 || ^5.0.0 925 + 911 926 astro@5.11.1: 912 927 resolution: {integrity: sha512-32dpUh0tXSV/FR2q2/z7LOA6IXl7RqET9J51IA0pPSSi3exhRP3EOSQGjBq10DzXT7VrvplDrFqwfiiWBS8oYA==} 913 928 engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} ··· 969 984 camelcase@8.0.0: 970 985 resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} 971 986 engines: {node: '>=16'} 987 + 988 + canvaskit-wasm@0.39.1: 989 + resolution: {integrity: sha512-Gy3lCmhUdKq+8bvDrs9t8+qf7RvcjuQn+we7vTVVyqgOVO1UVfHpsnBxkTZw+R4ApEJ3D5fKySl9TU11hmjl/A==} 990 + 991 + canvaskit-wasm@0.40.0: 992 + resolution: {integrity: sha512-Od2o+ZmoEw9PBdN/yCGvzfu0WVqlufBPEWNG452wY7E9aT8RBE+ChpZF526doOlg7zumO4iCS+RAeht4P0Gbpw==} 972 993 973 994 ccount@2.0.1: 974 995 resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} ··· 3175 3196 3176 3197 '@ungap/structured-clone@1.3.0': {} 3177 3198 3199 + '@webgpu/types@0.1.21': {} 3200 + 3178 3201 acorn-jsx@5.3.2(acorn@8.15.0): 3179 3202 dependencies: 3180 3203 acorn: 8.15.0 ··· 3233 3256 semver: 7.7.2 3234 3257 transitivePeerDependencies: 3235 3258 - supports-color 3259 + 3260 + astro-og-canvas@0.7.0(astro@5.11.1(@types/node@24.0.13)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.8.3)): 3261 + dependencies: 3262 + astro: 5.11.1(@types/node@24.0.13)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.8.3) 3263 + canvaskit-wasm: 0.39.1 3264 + deterministic-object-hash: 2.0.2 3265 + entities: 4.5.0 3236 3266 3237 3267 astro@5.11.1(@types/node@24.0.13)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.8.3): 3238 3268 dependencies: ··· 3395 3425 callsites@3.1.0: {} 3396 3426 3397 3427 camelcase@8.0.0: {} 3428 + 3429 + canvaskit-wasm@0.39.1: 3430 + dependencies: 3431 + '@webgpu/types': 0.1.21 3432 + 3433 + canvaskit-wasm@0.40.0: 3434 + dependencies: 3435 + '@webgpu/types': 0.1.21 3398 3436 3399 3437 ccount@2.0.1: {} 3400 3438
public/chiri-og.png public/og/chiri-og.png
public/images/screenshot-dark.png public/screenshots/screenshot-dark.png
public/images/screenshot-light.png public/screenshots/screenshot-light.png
public/og/og-bg.png

This is a binary file and will not be displayed.

public/og/og-logo.png

This is a binary file and will not be displayed.

+4 -3
src/components/layout/BaseHead.astro
··· 7 7 8 8 const canonicalURL = new URL(Astro.url.pathname, Astro.site) 9 9 10 - const { title, description } = Astro.props as BaseHeadProps 10 + const { title, description, ogImage } = Astro.props as BaseHeadProps 11 + const imageUrl = ogImage ? new URL(ogImage, Astro.url) : new URL('/og/chiri-og.png', Astro.url) 11 12 --- 12 13 13 14 <!-- Global Metadata --> ··· 47 48 <meta property="og:url" content={Astro.url} /> 48 49 <meta property="og:title" content={title} /> 49 50 <meta property="og:description" content={description} /> 50 - <meta property="og:image" content={new URL('/chiri-og.png', Astro.url)} /> 51 + <meta property="og:image" content={imageUrl} /> 51 52 52 53 <!-- Twitter --> 53 54 <meta property="twitter:card" content="summary_large_image" /> 54 55 <meta property="twitter:url" content={Astro.url} /> 55 56 <meta property="twitter:title" content={title} /> 56 57 <meta property="twitter:description" content={description} /> 57 - <meta property="twitter:image" content={new URL('/chiri-og.png', Astro.url)} /> 58 + <meta property="twitter:image" content={imageUrl} />
+4
src/layouts/PostLayout.astro
··· 17 17 import { themeConfig } from '@/config' 18 18 19 19 const { title, pubDate, readingTime, toc } = Astro.props as PostLayoutProps 20 + 21 + const postSlug = Astro.url.pathname.split('/').filter(Boolean).pop() || '' 22 + const ogImage = `/open-graph/${postSlug}.png` 20 23 --- 21 24 22 25 <BaseLayout ··· 27 30 <BaseHead 28 31 title={`${title} · ${themeConfig.site.title}`} 29 32 description={themeConfig.site.description} 33 + ogImage={ogImage} 30 34 slot="head" 31 35 /> 32 36 <div class="post-container">
+49
src/pages/open-graph/[...route].ts
··· 1 + import { getCollection } from 'astro:content' 2 + import { OGImageRoute } from 'astro-og-canvas' 3 + import { themeConfig } from '../../config' 4 + 5 + const collectionEntries = await getCollection('posts') 6 + 7 + // Map the array of content collection entries to create an object. 8 + // Converts [{ id: 'post.md', data: { title: 'Example', pubDate: Date } }] 9 + // to { 'post.md': { title: 'Example', pubDate: Date } } 10 + const pages = Object.fromEntries( 11 + collectionEntries.map(({ id, data }) => [id.replace(/\.(md|mdx)$/, ''), data]) 12 + ) 13 + 14 + export const { getStaticPaths, GET } = OGImageRoute({ 15 + param: 'route', 16 + pages, 17 + getImageOptions: (_path, page) => ({ 18 + title: page.title, 19 + description: themeConfig.site.title, 20 + logo: { 21 + path: 'public/og/og-logo.png', 22 + size: [80, 80] 23 + }, 24 + bgGradient: [[255, 255, 255]], 25 + bgImage: { 26 + path: 'public/og/og-bg.png', 27 + fit: 'fill' 28 + }, 29 + padding: 64, 30 + font: { 31 + title: { 32 + color: [28, 28, 28], 33 + size: 68, 34 + weight: 'SemiBold', 35 + families: ['Inter'] 36 + }, 37 + description: { 38 + color: [180, 180, 180], 39 + size: 40, 40 + weight: 'Medium', 41 + families: ['Inter'] 42 + } 43 + }, 44 + fonts: [ 45 + 'https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-600-normal.ttf', 46 + 'https://cdn.jsdelivr.net/fontsource/fonts/inter@latest/latin-500-normal.ttf' 47 + ] 48 + }) 49 + })
+1
src/types/component.types.ts
··· 30 30 export interface BaseHeadProps { 31 31 title: string 32 32 description: string 33 + ogImage?: string 33 34 } 34 35 35 36 // ImageOptimizer component props interface