Dev Diary: Building godegit.dev - Phase 4: Page Architecture & Dynamic Routing
From components to complete pages: implementing dynamic routing, SEO optimization, and content-first layouts. Learn how we built scalable page architecture with Nuxt 3's file-based routing and content management.


Nuxt's file-based routing system organizing our page architecture
From Components to Complete Experiences
Welcome back to our godegit.dev development diary! With our component library battle-tested and green, Phase 4 tackles the exciting challenge of assembling these pieces into complete, dynamic pages. This is where individual components become cohesive user experiences.
Phase 4 taught us that great pages aren't just collections of components—they're carefully orchestrated experiences that guide users through content while maintaining performance and accessibility standards.
The Page Assembly Challenge
Our testing framework had already defined what success looked like:
- Landing page must load in under 1 second with perfect brand compliance
- Dynamic documentation must handle any content structure while maintaining SEO
- Blog routing must provide excellent reading experience with proper typography
- Error handling must be helpful and brand-consistent
- Layout system must provide consistent experience across all pages
Each page needed to pass both component-level tests and full integration tests.
Page 1: The Landing Page - First Impressions Matter
Architecture Decisions Driven by Performance Tests
Our performance tests demanded aggressive optimization:
// tests/e2e/performance.spec.ts
test('landing page should meet Core Web Vitals', async ({ page }) => {
await page.goto('/')
// Largest Contentful Paint < 2.5s
const lcp = await page.evaluate(() => {
return new Promise(resolve => {
new PerformanceObserver(list => {
const entries = list.getEntries()
const lcpEntry = entries[entries.length - 1]
resolve(lcpEntry.startTime)
}).observe({ type: 'largest-contentful-paint', buffered: true })
})
})
expect(lcp).toBeLessThan(2500)
})
This test drove us toward a specific landing page architecture:
<!-- pages/index.vue -->
<template>
<div>
<!-- Hero Section - Above the fold priority -->
<MarketingHero />
<!-- Features Section - Lazy loaded -->
<MarketingFeatures />
<!-- Installation Section - Critical for conversion -->
<MarketingInstallation />
<!-- Final CTA Section -->
<section class="bg-brand-dark py-16 sm:py-20 lg:py-24">
<div class="mx-auto max-w-7xl px-4 text-center sm:px-6 lg:px-8">
<h2
class="text-brand-light mb-6 font-heading text-3xl font-bold sm:text-4xl lg:text-5xl"
>
Ready to Speed Up Your Git Workflow?
</h2>
<!-- Quick stats -->
<div class="mx-auto grid max-w-4xl grid-cols-1 gap-8 md:grid-cols-3">
<div class="text-center">
<div class="text-brand-accent mb-2 font-heading text-3xl font-bold">
10x
</div>
<div class="text-brand-light/80">Faster Downloads</div>
</div>
<div class="text-center">
<div class="text-brand-accent mb-2 font-heading text-3xl font-bold">
95%
</div>
<div class="text-brand-light/80">Less Bandwidth</div>
</div>
<div class="text-center">
<div class="text-brand-accent mb-2 font-heading text-3xl font-bold">
Zero
</div>
<div class="text-brand-light/80">Configuration</div>
</div>
</div>
</div>
</section>
</div>
</template>
<script setup lang="ts">
// SEO and meta configuration
useSeoMeta({
title: 'godegit - Fast Git Downloads Without History',
description:
'Download Git repository contents efficiently without the full history. Perfect for developers who value speed and simplicity. Get started in seconds.',
ogTitle: 'godegit - Fast Git Downloads Without History',
ogDescription:
'Download Git repository contents efficiently without the full history. Perfect for developers who value speed and simplicity.',
ogImage: '/og-image.png',
ogUrl: 'https://godegit.dev',
twitterCard: 'summary_large_image',
})
// Structured data for SEO
useHead({
script: [
{
type: 'application/ld+json',
children: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'godegit',
description: 'Fast Git repository downloads without the full history',
applicationCategory: 'DeveloperApplication',
operatingSystem: ['Linux', 'macOS', 'Windows'],
softwareVersion: '1.0.0',
downloadUrl: 'https://github.com/godegit/godegit/releases',
}),
},
],
})
</script>
The Results: Performance Goals Met
$ pnpm test tests/e2e/performance.spec.ts
✓ landing page should meet Core Web Vitals
✓ should have minimal layout shift (CLS < 0.1)
✓ should load critical resources efficiently
✓ should optimize image loading
Tests 4 passed (4)
Duration 2.1s
Page 2: Dynamic Documentation - Content Architecture at Scale
The Routing Challenge
Documentation sites have unique requirements:
- Flexible content structure (some docs have subsections, others don't)
- SEO optimization for every documentation page
- Navigation generation from content structure
- Search functionality (future requirement)
- Brand consistency across all content types
Nuxt's dynamic routing with [...slug].vue became our solution:
<!-- pages/docs/[...slug].vue -->
<template>
<div class="documentation-layout">
<div class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
<div class="lg:grid lg:grid-cols-4 lg:gap-8">
<!-- Documentation Navigation -->
<aside class="lg:col-span-1">
<ContentDocNav />
</aside>
<!-- Main Documentation Content -->
<div class="lg:col-span-3">
<div class="documentation-content">
<!-- Breadcrumb Navigation -->
<nav class="breadcrumb mb-6" aria-label="Breadcrumb">
<ol class="text-brand-muted flex items-center space-x-2 text-sm">
<li>
<NuxtLink
to="/docs"
class="hover:text-brand-accent transition-colors"
>
Documentation
</NuxtLink>
</li>
<li v-if="breadcrumbs.length > 0">
<!-- Dynamic breadcrumb generation -->
<span v-for="(crumb, index) in breadcrumbs" :key="index">
<svg
class="text-brand-muted mx-2 h-3 w-3"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 111.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"
/>
</svg>
<NuxtLink
v-if="index < breadcrumbs.length - 1"
:to="crumb.path"
>
{{ crumb.title }}
</NuxtLink>
<span v-else class="text-brand-dark font-medium">{{
crumb.title
}}</span>
</span>
</li>
</ol>
</nav>
<!-- Document Content -->
<article class="prose prose-lg max-w-none">
<ContentDoc v-slot="{ doc }" :path="$route.path">
<ContentRenderer :value="doc" />
</ContentDoc>
</article>
<!-- Navigation Footer -->
<footer
class="doc-navigation-footer mt-12 border-t border-gray-200 pt-8"
>
<div class="flex items-center justify-between">
<div v-if="prev">
<p class="text-brand-muted mb-1 text-sm">Previous</p>
<NuxtLink
:to="prev.path"
class="text-brand-accent hover:text-brand-accent/80 font-medium"
>
{{ prev.title }}
</NuxtLink>
</div>
<div v-if="next" class="text-right">
<p class="text-brand-muted mb-1 text-sm">Next</p>
<NuxtLink
:to="next.path"
class="text-brand-accent hover:text-brand-accent/80 font-medium"
>
{{ next.title }}
</NuxtLink>
</div>
</div>
</footer>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
// Fetch document data
const { data } = await useAsyncData('doc-' + route.path, () =>
queryContent(route.path)
.findOne()
.catch(() => null)
)
// Handle 404 for missing documentation
if (!data.value) {
throw createError({
statusCode: 404,
statusMessage: 'Documentation page not found',
})
}
// Generate breadcrumbs from route path
const breadcrumbs = computed(() => {
const pathSegments = route.path.split('/').filter(Boolean)
const crumbs = []
for (let i = 1; i < pathSegments.length; i++) {
const path = '/' + pathSegments.slice(0, i + 1).join('/')
const title = pathSegments[i]
.replace(/-/g, ' ')
.replace(/\b\w/g, l => l.toUpperCase())
crumbs.push({ path, title })
}
return crumbs
})
// Previous/Next navigation
const [prev, next] = await queryContent('docs')
.only(['_path', 'title'])
.findSurround(route.path)
// SEO configuration
useSeoMeta({
title: data.value?.title
? `${data.value.title} - godegit Documentation`
: 'godegit Documentation',
description:
data.value?.description ||
'Learn how to use godegit for fast Git repository downloads without history.',
})
</script>
Content Processing Innovation
One of our biggest breakthroughs was automated content processing. Instead of manually managing navigation and metadata, we built systems that extract structure from content:
// Content processing logic
const generateNavigation = async () => {
const docs = await queryContent('docs')
.only(['_path', 'title', 'category', 'order'])
.sort({ category: 1, order: 1 })
.find()
const navigation = {}
docs.forEach(doc => {
if (!navigation[doc.category]) {
navigation[doc.category] = []
}
navigation[doc.category].push({
title: doc.title,
path: doc._path,
})
})
return navigation
}
Page 3: Blog Architecture - Typography-First Design
The Reading Experience Challenge
Our blog tests demanded exceptional reading experience:
test('should provide excellent reading typography', async ({ page }) => {
await page.goto('/blog/dev-diary-phase-1-foundation')
// Test serif typography for body text
const blogContent = page.locator('.blog-content')
await expect(blogContent).toHaveCSS('font-family', /serif/)
// Test optimal line length for readability
const contentWidth = await blogContent.evaluate(el => el.offsetWidth)
expect(contentWidth).toBeLessThanOrEqual(800) // Optimal reading width
// Test proper heading hierarchy
const headings = await page.locator('h1, h2, h3').all()
expect(headings.length).toBeGreaterThan(0)
})
This drove us toward a typography-focused blog architecture:
<!-- pages/blog/[...slug].vue -->
<template>
<div class="blog-layout">
<div class="mx-auto max-w-4xl px-4 py-12 sm:px-6 lg:px-8">
<article v-if="data" class="blog-post">
<!-- Post Header -->
<header class="mb-12">
<div class="mb-6">
<!-- Categories/Tags -->
<div
v-if="data.tags && data.tags.length > 0"
class="mb-4 flex flex-wrap gap-2"
>
<span
v-for="tag in data.tags"
:key="tag"
class="bg-brand-accent/10 text-brand-accent inline-flex items-center rounded-full px-3 py-1 text-sm font-medium"
>
{{ tag }}
</span>
</div>
<!-- Title with serif typography -->
<h1
class="text-brand-dark mb-6 font-serif text-4xl font-bold leading-tight sm:text-5xl lg:text-6xl"
>
{{ data.title }}
</h1>
<!-- Subtitle/Description -->
<p
v-if="data.description"
class="text-brand-muted mb-8 font-serif text-xl leading-relaxed sm:text-2xl"
>
{{ data.description }}
</p>
</div>
<!-- Post Meta -->
<div
class="text-brand-muted flex items-center space-x-6 border-b border-gray-200 pb-6 text-sm"
>
<div v-if="data.author" class="flex items-center space-x-2">
<span class="font-medium">{{ data.author }}</span>
</div>
<div v-if="data.publishedAt" class="flex items-center space-x-1">
<time :datetime="data.publishedAt">{{
formatDate(data.publishedAt)
}}</time>
</div>
<div v-if="data.readingTime" class="flex items-center space-x-1">
<span>{{ data.readingTime }} min read</span>
</div>
</div>
</header>
<!-- Post Content with serif typography -->
<div class="blog-content prose prose-lg max-w-none">
<ContentDoc v-slot="{ doc }" :path="$route.path">
<ContentRenderer :value="doc" />
</ContentDoc>
</div>
</article>
</div>
</div>
</template>
<style scoped>
/* Blog-specific typography with serif fonts */
.blog-post h1,
.blog-post .font-serif {
font-family: Georgia, 'Times New Roman', serif;
}
/* Blog content prose styling with serif typography */
:deep(.blog-content.prose) {
font-family: Georgia, 'Times New Roman', serif;
line-height: 1.8;
}
:deep(.blog-content.prose p) {
@apply mb-6 text-lg leading-relaxed;
}
:deep(.blog-content.prose blockquote) {
@apply border-brand-accent my-8 border-l-4 bg-gray-50 py-4 pl-6 text-lg italic;
font-family: Georgia, 'Times New Roman', serif;
}
</style>
Page 4: Error Handling - When Things Go Wrong
User-Friendly Error Experience
Even our error pages needed to maintain brand consistency and provide helpful guidance:
<!-- error.vue -->
<template>
<div
class="error-page bg-brand-light flex min-h-screen items-center justify-center"
>
<div class="w-full max-w-md text-center">
<!-- Error illustration -->
<div class="mb-8">
<div
class="bg-brand-accent/10 mx-auto flex h-24 w-24 items-center justify-center rounded-full"
>
<svg
v-if="error.statusCode === 404"
class="text-brand-accent h-12 w-12"
>
<!-- 404 icon -->
</svg>
<svg v-else class="text-brand-accent h-12 w-12">
<!-- General error icon -->
</svg>
</div>
</div>
<!-- Error content -->
<div class="mb-8">
<h1
class="text-brand-dark mb-4 font-heading text-6xl font-bold sm:text-7xl"
>
{{ error.statusCode }}
</h1>
<h2
class="text-brand-dark mb-4 font-heading text-2xl font-semibold sm:text-3xl"
>
{{ errorTitle }}
</h2>
<p class="text-brand-muted text-lg leading-relaxed">
{{ errorMessage }}
</p>
</div>
<!-- Action buttons -->
<div class="flex flex-col justify-center gap-4 sm:flex-row">
<NuxtLink to="/" class="btn-primary">Go Home</NuxtLink>
<button @click="$router.go(-1)" class="btn-outline">Go Back</button>
</div>
<!-- Help links for 404 -->
<div
v-if="error.statusCode === 404"
class="mt-12 border-t border-gray-200 pt-8"
>
<p class="text-brand-muted mb-4 text-sm">
Looking for something specific?
</p>
<div class="flex flex-col gap-2 sm:flex-row sm:justify-center sm:gap-6">
<NuxtLink
to="/docs"
class="text-brand-accent hover:text-brand-accent/80 text-sm font-medium"
>
Documentation
</NuxtLink>
<NuxtLink
to="/blog"
class="text-brand-accent hover:text-brand-accent/80 text-sm font-medium"
>
Blog
</NuxtLink>
<a
href="https://github.com/godegit/godegit"
class="text-brand-accent hover:text-brand-accent/80 text-sm font-medium"
>
GitHub
</a>
</div>
</div>
</div>
</div>
</template>
Layout System: Consistency Across All Pages
The Default Layout Foundation
Our layout system provides consistent structure while allowing page-specific customization:
<!-- layouts/default.vue -->
<template>
<div class="bg-brand-light min-h-screen">
<!-- Header -->
<AppHeader />
<!-- Main content -->
<main role="main">
<slot />
</main>
<!-- Footer -->
<AppFooter />
</div>
</template>
<script setup lang="ts">
/**
* Default Layout
* Provides consistent structure across all pages
* Maintains brand color scheme and accessibility standards
*/
</script>
<style scoped>
/* Ensure proper layout structure */
.min-h-screen {
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
/* Focus management for accessibility */
main:focus {
@apply outline-none;
}
</style>
SEO Architecture: Built for Discovery
Comprehensive Meta Tag Management
Every page includes comprehensive SEO optimization:
// SEO composable for reusable meta tag management
export const useSEO = (options: {
title?: string
description?: string
image?: string
type?: string
url?: string
}) => {
const { $config } = useNuxtApp()
const route = useRoute()
const seoData = {
title: options.title || 'godegit - Fast Git Downloads Without History',
description:
options.description ||
'Download Git repository contents efficiently without the full history.',
image: options.image || '/og-image.png',
type: options.type || 'website',
url: options.url || `https://godegit.dev${route.path}`,
}
useSeoMeta({
title: seoData.title,
description: seoData.description,
ogTitle: seoData.title,
ogDescription: seoData.description,
ogImage: seoData.image,
ogUrl: seoData.url,
ogType: seoData.type,
twitterCard: 'summary_large_image',
twitterTitle: seoData.title,
twitterDescription: seoData.description,
twitterImage: seoData.image,
})
// Structured data for rich snippets
useHead({
script: [
{
type: 'application/ld+json',
children: JSON.stringify({
'@context': 'https://schema.org',
'@type': seoData.type === 'article' ? 'BlogPosting' : 'WebPage',
headline: seoData.title,
description: seoData.description,
image: seoData.image,
url: seoData.url,
}),
},
],
})
}
Performance Optimization Results
Our page-level optimizations delivered measurable results:
$ pnpm build && pnpm preview
# Lighthouse scores for key pages:
# Landing Page: 98 Performance, 100 Accessibility, 100 Best Practices, 100 SEO
# Documentation: 96 Performance, 100 Accessibility, 100 Best Practices, 100 SEO
# Blog Posts: 95 Performance, 100 Accessibility, 100 Best Practices, 100 SEO
Key optimizations that made the difference:
- Critical CSS Inlining: Above-the-fold styles loaded immediately
- Image Optimization: WebP/AVIF with proper sizing and lazy loading
- JavaScript Code Splitting: Route-based chunks for optimal loading
- Font Optimization: Preload critical fonts with
font-display: swap - Static Generation: Pre-rendered HTML for instant loading
Challenges and Solutions
Challenge 1: Dynamic Content SEO
Problem: How do you ensure dynamically generated pages have proper SEO?
Solution: We built SEO into the content processing pipeline:
## <!-- content/docs/installation.md -->
title: "Installation Guide" description: "Complete installation instructions for
godegit across all platforms" keywords: ["installation", "setup", "download",
"godegit"] category: "getting-started" order: 2
---
Every content file includes SEO metadata that gets automatically processed into proper meta tags.
Challenge 2: Route-Based Code Splitting
Problem: How do you optimize JavaScript loading without hurting user experience?
Solution: We used Nuxt's automatic code splitting with strategic preloading:
<!-- Preload critical routes -->
<template>
<div>
<NuxtLink to="/docs" prefetch>Documentation</NuxtLink>
<NuxtLink to="/blog" prefetch>Blog</NuxtLink>
</div>
</template>
Challenge 3: Content Processing Performance
Problem: How do you handle content processing without slowing down builds?
Solution: We implemented intelligent caching and incremental builds:
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
prerender: {
routes: ['/sitemap.xml'],
crawlLinks: true,
},
storage: {
content: {
driver: 'fs',
base: './content',
},
},
},
})
The Integration Victory
After implementing all page architecture, our integration tests showed the full system working together:
$ pnpm test tests/e2e/
✓ tests/e2e/landing-page.spec.ts (5 tests)
✓ tests/e2e/documentation.spec.ts (6 tests)
✓ tests/e2e/blog.spec.ts (4 tests)
✓ tests/e2e/responsive.spec.ts (8 tests)
✓ tests/e2e/performance.spec.ts (4 tests)
✓ tests/e2e/accessibility.spec.ts (7 tests)
Test Files 6 passed (6)
Tests 34 passed (34)
Duration 12.4s
Every page working perfectly across all devices and browsers.
What's Next: Phase 5 - Content Management
With our page architecture solid, Phase 5 will focus on empowering content creators with a powerful, brand-compliant content management system. We'll cover:
- Decap CMS integration for non-technical content editing
- Content validation to maintain brand standards
- Preview systems for safe content publishing
- Workflow management for content approval processes
Key Page Architecture Takeaways
- Performance is User Experience: Fast pages aren't just nice to have—they're essential for user engagement
- SEO as Architecture: Build SEO considerations into your routing and content systems from the start
- Typography Drives Design: Great reading experiences start with thoughtful typography choices
- Error States Matter: How you handle failure is part of your brand experience
- Content-First Routing: Let your content structure drive your routing decisions
Building scalable page architecture taught us that the best systems are invisible to users. When routing, SEO, and content management work seamlessly together, users can focus on what matters—consuming and engaging with your content.
Next time, we'll dive into content management systems and show how to empower content creators while maintaining technical and brand standards.
Live pages: Experience our page architecture at godegit.devPerformance scores: View our Lighthouse reports

