Skip to content

Commit

Permalink
feat: use lightweight og image generator
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Mar 21, 2024
1 parent 093ef7e commit 500ad57
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 103 deletions.
41 changes: 0 additions & 41 deletions app/components/OgImage/OgImageDocs.vue

This file was deleted.

31 changes: 31 additions & 0 deletions app/modules/og-image/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { addServerHandler, createResolver, defineNuxtModule } from 'nuxt/kit'

export default defineNuxtModule({
setup(_, nuxt) {
if (nuxt.options._prepare) {
return
}
const resolver = createResolver(import.meta.url)

addServerHandler({
route: '/_og/**',
handler: resolver.resolve('./runtime/handler'),
})

nuxt.options.nitro.serverAssets ??= []
nuxt.options.nitro.serverAssets.push({
baseName: 'og-image',
dir: resolver.resolve('./runtime/assets'),
})

nuxt.hook('nitro:init', (nitro) => {
nitro.hooks.hook('prerender:generate', (route) => {
if (route.route.startsWith('/_og/')) {
// temp patch for nitro prerenderer
route.route = route.route.split('?')[0]
route.fileName = route.fileName.split('?')[0]
}
})
})
},
})
Binary file added app/modules/og-image/runtime/assets/nunito.ttf
Binary file not shown.
29 changes: 29 additions & 0 deletions app/modules/og-image/runtime/assets/template.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 56 additions & 0 deletions app/modules/og-image/runtime/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { defineLazyEventHandler, setHeader, getQuery } from 'h3'
export default defineLazyEventHandler(async () => {
const { Resvg } = await import('@resvg/resvg-js')

// Read server assets
const nunito = await useStorage().getItemRaw('assets:og-image:nunito.ttf')
const svgTemplate = (await useStorage().getItem('assets:og-image:template.svg')) as string

return defineEventHandler((event) => {
// Use this for HMR
// const svgTemplate = (await useStorage().getItem('assets:og-image:template.svg')) as string

const { name = '', title = '', description = '' } = getQuery(event) as Record<string, string>

const descriptionLines = _wrapLine(decodeURIComponent(description), 60)
const svg = svgTemplate
.replace('!name', decodeURIComponent(name))
.replace('!title', decodeURIComponent(title))
.replace('!description1', descriptionLines[0] || '')
.replace('!description2', descriptionLines[1] || '')
.replace('!description3', descriptionLines[2] || '')

// https://github.com/yisibl/resvg-js
const resvg = new Resvg(svg, {
background: '',
dpi: 600,
fitTo: {
mode: 'width',
value: 1200,
},
font: {
// @ts-expect-error missing types
fontBuffers: [nunito],
},
})
const pngData = resvg.render()
const pngBuffer = pngData.asPng()

setHeader(event, 'Content-Type', 'image/png')
return pngBuffer
})
})

function _wrapLine(input: string, width: number) {
const lines: string[] = []
let line: string = ''
for (const word of input.split(' ')) {
if (line.length + word.length >= width) {
lines.push(line)
line = ''
}
line += word + ' '
}
lines.push(line)
return lines
}
7 changes: 1 addition & 6 deletions app/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const ssr = Boolean(isProd || process.env.NUXT_DOCS_SSR)

export default defineNuxtConfig({
ssr,
modules: ['@nuxt/fonts', 'nuxt-og-image', '@nuxt/content', isProd && '@nuxtjs/plausible', '@nuxt/ui'],
modules: ['@nuxt/fonts', '@nuxt/content', isProd && '@nuxtjs/plausible', '@nuxt/ui'],
ui: {
icons: [],
},
Expand Down Expand Up @@ -63,11 +63,6 @@ export default defineNuxtConfig({
uiPro: {
license: process.env.NUXT_UI_PRO_LICENSE || 'oss',
},
ogImage: {
enabled: ssr,
debug: false,
fonts: ['Nunito:400', 'Nunito:700'],
},
tailwindcss: {
viewer: dev,
quiet: !dev,
Expand Down
50 changes: 17 additions & 33 deletions app/pages/[...slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,12 @@ const { data: surround } = await useAsyncData(`${route.path}-surround`, () =>
.findSurround(withoutTrailingSlash(route.path)),
)
useSeoMeta({
title: page.value?.title,
usePageSEO({
title: `${page.value?.title} - ${appConfig.site.name}`,
ogTitle: page.value?.title,
description: page.value?.description,
})
if (process.server) {
defineOgImageComponent('OgImageDocs')
}
const headline = computed(() => findPageHeadline(page.value))
const tocOpen = ref(false)
Expand All @@ -57,22 +54,12 @@ onMounted(() => {
<UPageHeader :title="page.title" :description="page.description" :links="page.links" :headline="headline">
</UPageHeader>

<div
v-if="tocLinks.length > 1"
class="float-right mt-4 top-[calc(var(--header-height)_+_0.5rem)] z-10 flex justify-end sticky"
>
<UDropdown
:items="[[{ label: 'Return to top', click: scrollToTop }], tocLinks]"
:popper="{ placement: 'bottom-end' }"
:mode="isMobile ? 'click' : 'hover'"
v-model:open="tocOpen"
>
<UButton
color="white"
label="On this page"
:trailing="false"
:icon="`i-heroicons-chevron-${tocOpen ? 'down' : 'left'}-20-solid`"
/>
<div v-if="tocLinks.length > 1"
class="float-right mt-4 top-[calc(var(--header-height)_+_0.5rem)] z-10 flex justify-end sticky">
<UDropdown :items="[[{ label: 'Return to top', click: scrollToTop }], tocLinks]"
:popper="{ placement: 'bottom-end' }" :mode="isMobile ? 'click' : 'hover'" v-model:open="tocOpen">
<UButton color="white" label="On this page" :trailing="false"
:icon="`i-heroicons-chevron-${tocOpen ? 'down' : 'left'}-20-solid`" />
</UDropdown>
</div>

Expand All @@ -84,17 +71,14 @@ onMounted(() => {
<div class="space-y-6">
<UDivider type="dashed" />
<div class="mb-4">
<UPageLinks
class="inline-block"
:links="[
{
icon: 'i-ph-pen-duotone',
label: 'Edit this page on GitHub',
to: `https://github.com/${appConfig.docs.github}/edit/main/docs/${page._file}`,
target: '_blank',
},
]"
/>
<UPageLinks class="inline-block" :links="[
{
icon: 'i-ph-pen-duotone',
label: 'Edit this page on GitHub',
to: `https://github.com/${appConfig.docs.github}/edit/main/docs/${page._file}`,
target: '_blank',
},
]" />
</div>
<UContentSurround v-if="surround?.length" class="mb-4" :surround="surround" />
</div>
Expand Down
34 changes: 12 additions & 22 deletions app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,14 @@ if (!page.value) {
})
}
useHead({
titleTemplate: '%s %separator UnJS',
})
const appConfig = useAppConfig()
useSeoMeta({
title: page.value!.title,
usePageSEO({
title: `${appConfig.site.name} - ${page.value!.heroSubtitle}`,
ogTitle: page.value!.heroSubtitle,
description: page.value!.description,
})
if (process.server) {
defineOgImageComponent('OgImageDocs')
}
function nornalizeHeroLinks(links: LandingConfig['heroLinks']) {
return Object.entries(links || {})
.map(([key, link], order) => {
Expand Down Expand Up @@ -91,19 +86,14 @@ const hero = computed(() => {
<template v-if="page.features?.length > 0 && !hero.withFeatures">
<ULandingSection :title="page.featuresTitle">
<UPageGrid>
<ULandingCard
v-for="(item, index) of page.features"
:key="index"
v-bind="item"
:ui="{
icon: {
// If the icon is an emoji, we need to use a bigger size
base: /\p{Emoji}/u.test(item.icon)
? '!text-2xl !w-auto !h-auto'
: 'w-8 h-8 flex-shrink-0 text-gray-900 dark:text-white',
},
}"
>
<ULandingCard v-for="(item, index) of page.features" :key="index" v-bind="item" :ui="{
icon: {
// If the icon is an emoji, we need to use a bigger size
base: /\p{Emoji}/u.test(item.icon)
? '!text-2xl !w-auto !h-auto'
: 'w-8 h-8 flex-shrink-0 text-gray-900 dark:text-white',
},
}">
<template v-if="item.description" #description>
<MDC :value="item.description" tag="p" class="prose prose-primary dark:prose-invert" />
</template>
Expand Down
47 changes: 47 additions & 0 deletions app/utils/seo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export interface PageMeta {
title: string
ogTitle: string
description: string
}

export function usePageSEO(page: PageMeta) {
if (!process.server && !import.meta.dev) {
return
}

const route = useRoute()
const appConfig = useAppConfig()

const path = route.path === '/' ? '/_index' : route.path
const canonicalURL = import.meta.dev ? useRequestURL() : appConfig.site.url
const ogURL = new URL(`/_og${path}.png`, canonicalURL)

ogURL.searchParams.set('name', appConfig.site.name)
ogURL.searchParams.set('title', page.ogTitle || page.title || appConfig.site.name)
ogURL.searchParams.set('description', page.description || appConfig.site.description || '')

if (import.meta.prerender) {
prerenderRoutes(ogURL.pathname + ogURL.search)
ogURL.searchParams.delete('name')
ogURL.searchParams.delete('title')
ogURL.searchParams.delete('description')
}

useSeoMeta({
title: page.title,
description: page.description,
ogImage: {
url: ogURL.href,
width: 1200,
height: 600,
type: 'image/png',
alt: page.description || appConfig.site.description,
},
twitterImage: {
url: ogURL.href,
width: 1200,
height: 600,
alt: page.description || appConfig.site.description,
},
})
}
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@nuxt/ui-pro": "^1.0.2",
"@nuxtjs/plausible": "^0.2.4",
"@nuxtjs/tailwindcss": "^6.11.4",
"@resvg/resvg-js": "^2.6.0",
"automd": "^0.3.6",
"c12": "^1.10.0",
"citty": "^0.1.6",
Expand All @@ -45,7 +46,6 @@
"nuxi": "^3.10.1",
"nuxt": "^3.11.0",
"nuxt-build-cache": "^0.1.1",
"nuxt-og-image": "^3.0.0-rc.47",
"pkg-types": "^1.0.3",
"scule": "^1.3.0",
"tailwindcss": "^3.4.1",
Expand Down

0 comments on commit 500ad57

Please sign in to comment.