SEO Improvements Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Maximize SEO with build-time generated og-image, redesigned favicon, social meta tags, font preloading, and enhanced structured data.

Architecture: TypeScript scripts in scripts/ generate all image assets at build time. satori renders declarative markup to SVG (for og-image with text), sharp converts SVG to PNG at various sizes, png-to-ico creates multi-size ICO files. The build script chains asset generation before VitePress build.

Tech Stack: satori, sharp, png-to-ico, tsx (devDependencies)


File Structure

ActionPathResponsibility
Createscripts/generate-assets.tsEntry point — runs both generators
Createscripts/generate-og-image.tsGenerates public/og-image.png via satori + sharp
Createscripts/generate-favicon.tsGenerates all favicon variants from SVG
Modifypublic/favicon.svgRedesigned spider icon
Modifypackage.jsonAdd devDependencies and generate-assets script
Modify.vitepress/config.tsMeta tags, resource hints, structured data
Generatepublic/og-image.png1200x630 social sharing image
Generatepublic/favicon.ico16x16 + 32x32 multi-size
Generatepublic/favicon-192x192.pngPWA icon
Generatepublic/favicon-512x512.pngPWA large icon
Generatepublic/apple-touch-icon.png180x180 Apple icon

Task 1: Install Dependencies

Files:

  • Modify: package.json

  • [ ] Step 1: Install devDependencies

bash
npm install --save-dev satori sharp png-to-ico tsx
  • [ ] Step 2: Add generate-assets script to package.json

In package.json, add/modify the scripts section:

json
{
  "scripts": {
    "dev": "vitepress dev",
    "build": "npm run generate-assets && vitepress build",
    "preview": "vitepress preview",
    "generate-assets": "tsx scripts/generate-assets.ts"
  }
}
  • [ ] Step 3: Commit
bash
git add package.json package-lock.json
git commit -m "chore: add satori, sharp, png-to-ico, tsx for asset generation"

Task 2: Redesign Favicon SVG

Files:

  • Modify: public/favicon.svg

  • [ ] Step 1: Replace favicon.svg with new spider design

The new design should be a clearer spider icon that reads well at 16x16. Key improvements over current:

  • Slightly larger body proportions within the 32x32 viewBox
  • Thicker leg strokes (2px instead of 1.5px) for visibility at small sizes
  • More defined leg curves with better spacing
  • Keep the two-circle body + eyes personality

Replace public/favicon.svg with:

svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
  <!-- Body: head + abdomen -->
  <circle cx="16" cy="12" r="4.5" fill="#8b5cf6"/>
  <ellipse cx="16" cy="21" rx="5.5" ry="6" fill="#8b5cf6"/>

  <!-- Legs: 4 pairs, thicker strokes for small-size readability -->
  <!-- Front legs (up and out) -->
  <path d="M12.5 11 Q7 6 3 4" stroke="#8b5cf6" stroke-width="2" stroke-linecap="round" fill="none"/>
  <path d="M19.5 11 Q25 6 29 4" stroke="#8b5cf6" stroke-width="2" stroke-linecap="round" fill="none"/>

  <!-- Upper-mid legs (out and slightly down) -->
  <path d="M11.5 14 Q5 13 1 12" stroke="#8b5cf6" stroke-width="2" stroke-linecap="round" fill="none"/>
  <path d="M20.5 14 Q27 13 31 12" stroke="#8b5cf6" stroke-width="2" stroke-linecap="round" fill="none"/>

  <!-- Lower-mid legs (out and down) -->
  <path d="M11 19 Q5 22 2 25" stroke="#8b5cf6" stroke-width="2" stroke-linecap="round" fill="none"/>
  <path d="M21 19 Q27 22 30 25" stroke="#8b5cf6" stroke-width="2" stroke-linecap="round" fill="none"/>

  <!-- Back legs (down and out) -->
  <path d="M12.5 24 Q8 28 5 31" stroke="#8b5cf6" stroke-width="2" stroke-linecap="round" fill="none"/>
  <path d="M19.5 24 Q24 28 27 31" stroke="#8b5cf6" stroke-width="2" stroke-linecap="round" fill="none"/>

  <!-- Eyes -->
  <circle cx="14.2" cy="11" r="1.2" fill="#0a0a0f"/>
  <circle cx="17.8" cy="11" r="1.2" fill="#0a0a0f"/>
</svg>
  • [ ] Step 2: Verify the SVG renders correctly

Open public/favicon.svg in a browser or preview tool. Confirm:

  • Spider is clearly recognizable

  • 8 legs visible and evenly spaced

  • Eyes are visible

  • Purple (#8b5cf6) color is correct

  • [ ] Step 3: Commit

bash
git add public/favicon.svg
git commit -m "feat: redesign favicon with clearer spider icon"

Task 3: Create Favicon Generation Script

Files:

  • Create: scripts/generate-favicon.ts

  • [ ] Step 1: Create the favicon generation script

Create scripts/generate-favicon.ts:

typescript
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import sharp from 'sharp'
import pngToIco from 'png-to-ico'

const PUBLIC_DIR = resolve(import.meta.dirname, '..', 'public')

const SIZES = {
  'favicon-192x192.png': 192,
  'favicon-512x512.png': 512,
  'apple-touch-icon.png': 180,
} as const

export async function generateFavicon(): Promise<void> {
  const svgPath = resolve(PUBLIC_DIR, 'favicon.svg')
  const svgBuffer = await readFile(svgPath)

  // Generate PNG variants
  const pngPromises = Object.entries(SIZES).map(async ([filename, size]) => {
    const pngBuffer = await sharp(svgBuffer)
      .resize(size, size)
      .png()
      .toBuffer()

    await sharp(pngBuffer).toFile(resolve(PUBLIC_DIR, filename))
    console.log(`  ✓ ${filename} (${size}x${size})`)
    return { filename, buffer: pngBuffer }
  })

  const results = await Promise.all(pngPromises)

  // Generate ICO from 16x16 and 32x32 PNGs
  const ico16 = await sharp(svgBuffer).resize(16, 16).png().toBuffer()
  const ico32 = await sharp(svgBuffer).resize(32, 32).png().toBuffer()
  const icoBuffer = await pngToIco([ico16, ico32])
  await sharp(icoBuffer).toFile(resolve(PUBLIC_DIR, 'favicon.ico'))
  console.log('  ✓ favicon.ico (16x16 + 32x32)')
}
  • [ ] Step 2: Verify script compiles
bash
npx tsx --eval "import('./scripts/generate-favicon.ts').then(m => console.log('OK: exports', Object.keys(m)))"

Expected: OK: exports [ 'generateFavicon' ]

  • [ ] Step 3: Commit
bash
git add scripts/generate-favicon.ts
git commit -m "feat: add favicon generation script"

Task 4: Create OG Image Generation Script

Files:

  • Create: scripts/generate-og-image.ts

  • [ ] Step 1: Create the og-image generation script

Create scripts/generate-og-image.ts:

typescript
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
import satori from 'satori'
import sharp from 'sharp'

const PUBLIC_DIR = resolve(import.meta.dirname, '..', 'public')
const FONTS_DIR = resolve(PUBLIC_DIR, 'fonts')

const WIDTH = 1200
const HEIGHT = 630

// Spider icon as SVG path data (simplified from favicon for embedding)
const SPIDER_SVG = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
  <circle cx="16" cy="12" r="4.5" fill="#c4b5fd"/>
  <ellipse cx="16" cy="21" rx="5.5" ry="6" fill="#c4b5fd"/>
  <path d="M12.5 11 Q7 6 3 4" stroke="#c4b5fd" stroke-width="2" stroke-linecap="round" fill="none"/>
  <path d="M19.5 11 Q25 6 29 4" stroke="#c4b5fd" stroke-width="2" stroke-linecap="round" fill="none"/>
  <path d="M11.5 14 Q5 13 1 12" stroke="#c4b5fd" stroke-width="2" stroke-linecap="round" fill="none"/>
  <path d="M20.5 14 Q27 13 31 12" stroke="#c4b5fd" stroke-width="2" stroke-linecap="round" fill="none"/>
  <path d="M11 19 Q5 22 2 25" stroke="#c4b5fd" stroke-width="2" stroke-linecap="round" fill="none"/>
  <path d="M21 19 Q27 22 30 25" stroke="#c4b5fd" stroke-width="2" stroke-linecap="round" fill="none"/>
  <path d="M12.5 24 Q8 28 5 31" stroke="#c4b5fd" stroke-width="2" stroke-linecap="round" fill="none"/>
  <path d="M19.5 24 Q24 28 27 31" stroke="#c4b5fd" stroke-width="2" stroke-linecap="round" fill="none"/>
  <circle cx="14.2" cy="11" r="1.2" fill="#0a0a0f"/>
  <circle cx="17.8" cy="11" r="1.2" fill="#0a0a0f"/>
</svg>
`.trim()

// Decorative web lines as SVG overlay
const WEB_LINES_SVG = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${WIDTH} ${HEIGHT}" fill="none">
  <!-- Corner web lines -->
  <path d="M0 0 Q200 80 350 200" stroke="#8b5cf6" stroke-width="1" opacity="0.15" fill="none"/>
  <path d="M0 0 Q100 150 180 300" stroke="#8b5cf6" stroke-width="1" opacity="0.1" fill="none"/>
  <path d="M${WIDTH} 0 Q${WIDTH - 200} 80 ${WIDTH - 350} 200" stroke="#8b5cf6" stroke-width="1" opacity="0.15" fill="none"/>
  <path d="M${WIDTH} 0 Q${WIDTH - 100} 150 ${WIDTH - 180} 300" stroke="#8b5cf6" stroke-width="1" opacity="0.1" fill="none"/>
  <path d="M0 ${HEIGHT} Q200 ${HEIGHT - 80} 350 ${HEIGHT - 200}" stroke="#8b5cf6" stroke-width="1" opacity="0.1" fill="none"/>
  <path d="M${WIDTH} ${HEIGHT} Q${WIDTH - 200} ${HEIGHT - 80} ${WIDTH - 350} ${HEIGHT - 200}" stroke="#8b5cf6" stroke-width="1" opacity="0.1" fill="none"/>
  <!-- Radial web circles -->
  <circle cx="${WIDTH / 2}" cy="${HEIGHT / 2}" r="250" stroke="#8b5cf6" stroke-width="0.5" opacity="0.08" fill="none"/>
  <circle cx="${WIDTH / 2}" cy="${HEIGHT / 2}" r="180" stroke="#8b5cf6" stroke-width="0.5" opacity="0.06" fill="none"/>
</svg>
`.trim()

export async function generateOgImage(): Promise<void> {
  // Load fonts
  const [cinzelFont, interFont] = await Promise.all([
    readFile(resolve(FONTS_DIR, 'cinzel-v23-latin-700.woff2')),
    readFile(resolve(FONTS_DIR, 'inter-v18-latin-400.woff2')),
  ])

  // Generate text layout with satori
  const svg = await satori(
    {
      type: 'div',
      props: {
        style: {
          width: '100%',
          height: '100%',
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          justifyContent: 'center',
          backgroundColor: '#0a0a0f',
          background: 'radial-gradient(ellipse at center, #1a1028 0%, #0a0a0f 70%)',
        },
        children: [
          {
            type: 'div',
            props: {
              style: {
                fontSize: 72,
                fontFamily: 'Cinzel',
                fontWeight: 700,
                color: '#e9e5f0',
                letterSpacing: '0.04em',
                marginTop: 80,
              },
              children: 'Aranea Development',
            },
          },
          {
            type: 'div',
            props: {
              style: {
                width: 120,
                height: 2,
                backgroundColor: '#8b5cf6',
                marginTop: 24,
                marginBottom: 24,
                borderRadius: 1,
              },
            },
          },
          {
            type: 'div',
            props: {
              style: {
                fontSize: 28,
                fontFamily: 'Inter',
                color: '#a89cc0',
                letterSpacing: '0.02em',
              },
              children: 'Solo developer building tools for developers',
            },
          },
        ],
      },
    },
    {
      width: WIDTH,
      height: HEIGHT,
      fonts: [
        { name: 'Cinzel', data: cinzelFont, weight: 700, style: 'normal' },
        { name: 'Inter', data: interFont, weight: 400, style: 'normal' },
      ],
    },
  )

  // Render satori SVG to base PNG
  const basePng = await sharp(Buffer.from(svg)).png().toBuffer()

  // Render spider icon to PNG (centered, upper area — 120x120 at y=40)
  const spiderPng = await sharp(Buffer.from(SPIDER_SVG))
    .resize(120, 120)
    .png()
    .toBuffer()

  // Render web lines overlay
  const webLinesPng = await sharp(Buffer.from(WEB_LINES_SVG))
    .resize(WIDTH, HEIGHT)
    .png()
    .toBuffer()

  // Composite all layers: base + web lines + spider
  await sharp(basePng)
    .composite([
      { input: webLinesPng, top: 0, left: 0 },
      { input: spiderPng, top: 40, left: Math.round((WIDTH - 120) / 2) },
    ])
    .png({ quality: 90 })
    .toFile(resolve(PUBLIC_DIR, 'og-image.png'))

  console.log(`  ✓ og-image.png (${WIDTH}x${HEIGHT})`)
}
  • [ ] Step 2: Verify script compiles
bash
npx tsx --eval "import('./scripts/generate-og-image.ts').then(m => console.log('OK: exports', Object.keys(m)))"

Expected: OK: exports [ 'generateOgImage' ]

  • [ ] Step 3: Commit
bash
git add scripts/generate-og-image.ts
git commit -m "feat: add og-image generation script"

Task 5: Create Entry Point Script

Files:

  • Create: scripts/generate-assets.ts

  • [ ] Step 1: Create the entry point

Create scripts/generate-assets.ts:

typescript
import { generateFavicon } from './generate-favicon.js'
import { generateOgImage } from './generate-og-image.js'

async function main() {
  console.log('Generating assets...')

  console.log('\nFavicons:')
  await generateFavicon()

  console.log('\nOG Image:')
  await generateOgImage()

  console.log('\nDone!')
}

main().catch((err) => {
  console.error('Asset generation failed:', err)
  process.exit(1)
})
  • [ ] Step 2: Run the full generation
bash
npm run generate-assets

Expected output:

Generating assets...

Favicons:
  ✓ favicon-192x192.png (192x192)
  ✓ favicon-512x512.png (512x512)
  ✓ apple-touch-icon.png (180x180)
  ✓ favicon.ico (16x16 + 32x32)

OG Image:
  ✓ og-image.png (1200x630)

Done!
  • [ ] Step 3: Verify generated files
bash
ls -la public/og-image.png public/favicon.ico public/favicon-192x192.png public/favicon-512x512.png public/apple-touch-icon.png

All files should exist with reasonable sizes (og-image ~50-200KB, PNGs ~5-50KB each).

  • [ ] Step 4: Visually verify og-image.png

Open public/og-image.png and confirm:

  • Dark background with purple gradient

  • Spider icon in upper area

  • "Aranea Development" in Cinzel font

  • Tagline below in Inter

  • Subtle web line decorations

  • Clean, professional look at 1200x630

  • [ ] Step 5: Visually verify favicon

Open public/favicon.svg and public/favicon-192x192.png. Confirm:

  • Spider is recognizable

  • Colors match brand

  • PNG is crisp at 192x192

  • [ ] Step 6: Commit

bash
git add scripts/generate-assets.ts public/og-image.png public/favicon.ico public/favicon-192x192.png public/favicon-512x512.png public/apple-touch-icon.png
git commit -m "feat: add asset generation entry point and generated assets"

Task 6: Update VitePress Config — Meta Tags & Resource Hints

Files:

  • Modify: .vitepress/config.ts

  • [ ] Step 1: Add og:image, twitter:image, and resource hints

In .vitepress/config.ts, in the head array, add after the existing Twitter Card entries:

typescript
// Open Graph Image
['meta', { property: 'og:image', content: `${siteUrl}/og-image.png` }],
['meta', { property: 'og:image:width', content: '1200' }],
['meta', { property: 'og:image:height', content: '630' }],
['meta', { property: 'og:image:type', content: 'image/png' }],

Add twitter:image after the existing twitter entries:

typescript
['meta', { name: 'twitter:image', content: `${siteUrl}/og-image.png` }],

Change the existing twitter:card from summary to summary_large_image:

typescript
['meta', { name: 'twitter:card', content: 'summary_large_image' }],

Add font preload hints after the favicon link entries (before meta tags):

typescript
// Font preloads
['link', { rel: 'preload', href: '/fonts/cinzel-v23-latin-700.woff2', as: 'font', type: 'font/woff2', crossorigin: '' }],
['link', { rel: 'preload', href: '/fonts/inter-v18-latin-400.woff2', as: 'font', type: 'font/woff2', crossorigin: '' }],
  • [ ] Step 2: Verify config is valid
bash
npx tsx --eval "import('./.vitepress/config.ts').then(m => console.log('Config OK'))"

Expected: Config OK

  • [ ] Step 3: Commit
bash
git add .vitepress/config.ts
git commit -m "feat: add og:image, twitter:image, font preloads to head config"

Task 7: Update VitePress Config — Structured Data Enhancements

Files:

  • Modify: .vitepress/config.ts

  • [ ] Step 1: Add Organization schema and enhance existing schemas

In .vitepress/config.ts, in the @graph array inside the JSON-LD script tag, add the Organization schema after the Person schema:

typescript
{
  '@type': 'Organization',
  '@id': `${siteUrl}/#organization`,
  name: 'Aranea Development',
  url: siteUrl,
  logo: {
    '@type': 'ImageObject',
    url: `${siteUrl}/favicon-512x512.png`,
    width: 512,
    height: 512,
  },
  image: `${siteUrl}/og-image.png`,
  founder: { '@id': `${siteUrl}/#person` },
  sameAs: ['https://github.com/AraneaDev'],
},

Add publisher to the existing WebSite schema:

typescript
publisher: { '@id': `${siteUrl}/#organization` },

Add image to the existing WebSite schema:

typescript
image: `${siteUrl}/og-image.png`,

Add worksFor to the existing Person schema:

typescript
worksFor: { '@id': `${siteUrl}/#organization` },
  • [ ] Step 2: Validate the JSON-LD is well-formed
bash
npx tsx --eval "
  import config from './.vitepress/config.ts';
  const head = config.default?.head || config.head || [];
  const jsonLd = head.find(h => h[1]?.type === 'application/ld+json');
  const data = JSON.parse(jsonLd[2]);
  console.log('Types:', data['@graph'].map(n => n['@type']));
  console.log('Organization:', data['@graph'].find(n => n['@type'] === 'Organization')?.name);
  console.log('WebSite publisher:', data['@graph'].find(n => n['@type'] === 'WebSite')?.publisher);
  console.log('Person worksFor:', data['@graph'].find(n => n['@type'] === 'Person')?.worksFor);
"

Expected output shows all 5 types (WebSite, Person, Organization, ItemList) with proper references.

  • [ ] Step 3: Commit
bash
git add .vitepress/config.ts
git commit -m "feat: add Organization schema, enhance structured data references"

Task 8: Full Build Verification

  • [ ] Step 1: Run full build
bash
npm run build

Expected: Asset generation runs first, then VitePress builds successfully.

  • [ ] Step 2: Verify meta tags in built HTML
bash
grep -E 'og:image|twitter:image|twitter:card|preload.*font|Organization' dist/index.html

Expected: All new meta tags present in the built HTML output.

  • [ ] Step 3: Verify generated assets are in dist
bash
ls -la dist/og-image.png dist/favicon.ico dist/favicon-192x192.png dist/favicon-512x512.png dist/apple-touch-icon.png dist/favicon.svg

All files should be present (VitePress copies public/ to dist/).

  • [ ] Step 4: Preview and visually verify
bash
npm run preview

Open the preview URL and check:

  • Favicon shows new spider icon in browser tab

  • View page source confirms all meta tags

  • Right-click → Inspect shows no console errors

  • [ ] Step 5: Commit any remaining changes

bash
git add -A
git commit -m "chore: verify full build with SEO improvements"