Split Navigation Build from Content Rendering
📋 Table of Contents
- 📖 Overview
- 🎯 Implementation Strategy
- 🗂️ File Structure Setup
- ⚙️ Configuration Files
- 🔧 Build Scripts and Orchestration
- 🚀 Development Workflow
- 🔄 Migration Process
- 🧪 Testing and Validation
- 📊 Performance Comparison
- 🛠️ Troubleshooting
📖 Overview
This document provides a step-by-step implementation guide for splitting Quarto navigation build from content rendering, based on Strategy 1: Content-Navigation Separation from the modular deployment architecture.
Prerequisites: Understanding of 001.002 Architecture - Monolithic vs. Modular Deployment.md
What You’ll Achieve:
- ✅ Independent content builds - update content without rebuilding navigation
- ✅ Individual page rendering - build single pages in 30-60 seconds
- ✅ Parallel builds - multiple content sections build simultaneously
- ✅ Faster development cycles - dramatically reduced feedback loops
Implementation Approach:
This guide shows how to transition from the current monolithic Learn repository structure to a modular architecture where navigation and content are built separately and composed at deployment time.
🎯 Implementation Strategy
Core Concept
The split architecture works by separating concerns:
Current Monolithic:
┌─────────────────────────────┐
│ Single Build Process │
│ ┌─────────────────────┐ │
│ │Content│ Nav │Shell │ │
│ │ Pages │ Menu │Layout│ │
│ └─────────────────────┘ │
└─────────────────────────────┘
Split Architecture:
┌─────────┐ ┌──────────┐ ┌─────────┐
│ Content │ │Navigation│ │ Shell │
│ Build │ │ Build │ │ Build │
│ │ │ │ │ │
└─────────┘ └──────────┘ └─────────┘
│ │ │
└────────────┼────────────┘
│
┌───────────────┐
│ Deployment │
│ Composition │
└───────────────┘
Build Process Flow
- Navigation Build: Creates site shell, menu structure, and API endpoints
- Content Builds: Generate content-only HTML pages (multiple independent builds)
- Composition: Merge navigation shell with content pages
- Deployment: Deploy composed site to target environment
Key Benefits
| Aspect | Before (Monolithic) | After (Split) |
|---|---|---|
| Single page edit | 2-5 minutes | 30-60 seconds |
| Navigation change | 2-5 minutes | 45 seconds |
| Team independence | Blocking | Independent |
| Development feedback | Slow | Fast |
🗂️ File Structure Setup
Target Directory Structure
Transform your current structure into this modular organization:
Learn-Modular/
├── navigation/ # Navigation-only builds
│ ├── _quarto.yml # Shell configuration
│ ├── index.qmd # Main landing page
│ ├── templates/
│ │ ├── shell-template.html
│ │ ├── content-wrapper.html
│ │ └── navigation-menu.html
│ ├── scripts/
│ │ ├── generate-navigation-api.ps1
│ │ ├── scan-content-structure.ps1
│ │ └── validate-links.ps1
│ ├── styles/
│ │ ├── navigation.scss
│ │ ├── shell.css
│ │ └── responsive.css
│ └── assets/
│ ├── navigation.js
│ └── search.js
├── content/ # Content-only builds
│ ├── build-2025/
│ │ ├── _quarto.yml # Content-specific config
│ │ ├── README.md # Section overview
│ │ ├── sessions/
│ │ │ ├── brk101.md
│ │ │ ├── brk103.md
│ │ │ └── brk114.md
│ │ └── assets/
│ │ └── build-2025.css
│ ├── azure-topics/
│ │ ├── _quarto.yml
│ │ ├── README.md
│ │ ├── guides/
│ │ │ ├── naming-conventions.md
│ │ │ ├── storage-options.md
│ │ │ └── cosmos-access.md
│ │ └── assets/
│ └── tools/
│ ├── _quarto.yml
│ ├── README.md
│ └── guides/
├── orchestration/ # Build coordination
│ ├── build-all.ps1
│ ├── build-navigation.ps1
│ ├── build-content-section.ps1
│ ├── dev-render-page.ps1
│ ├── compose-deployment.ps1
│ ├── deploy-to-github.ps1
│ └── watch-and-rebuild.ps1
├── deploy/ # Build outputs
│ ├── shell/ # Navigation output
│ │ ├── index.html
│ │ ├── api/
│ │ │ ├── navigation.json
│ │ │ └── sitemap.json
│ │ └── assets/
│ ├── content/ # Content outputs
│ │ ├── build-2025/
│ │ ├── azure-topics/
│ │ └── tools/
│ └── final/ # Composed deployment
│ ├── index.html
│ ├── content/
│ ├── api/
│ └── assets/
└── shared/ # Common resources
├── templates/
├── styles/
└── scripts/
Migration Steps
Step 1: Create Directory Structure
# Create new modular structure
New-Item -ItemType Directory -Path "navigation", "content", "orchestration", "deploy", "shared" -Force
# Create subdirectories
New-Item -ItemType Directory -Path "navigation/templates", "navigation/scripts", "navigation/styles", "navigation/assets" -Force
New-Item -ItemType Directory -Path "deploy/shell", "deploy/content", "deploy/final" -ForceStep 2: Move Existing Content
# Move content sections to content folder
Move-Item "202506 Build 2025" "content/build-2025"
Move-Item "20250702 Azure Naming conventions" "content/azure-topics/guides/"
Move-Item "20250827 what is yq overview" "content/tools/guides/"
# Extract navigation logic (manual process)
# Copy current _quarto.yml to navigation/_quarto.yml for modification⚙️ Configuration Files
Content Section Configuration
Create content-only configurations for each section:
# content/build-2025/_quarto.yml
project:
type: website
output-dir: ../../deploy/content/build-2025
# Build ALL content in this section
render:
- "README.md" # Section overview
- "sessions/*.md" # All session files
- "**/*.md" # Any nested content
# Minimal format - no navigation overhead
format:
html:
theme: cosmo
template-partials:
- "../../shared/templates/content-wrapper.html"
css:
- "../../shared/styles/content.css"
- "assets/build-2025.css"
toc: true
toc-depth: 3
embed-resources: false
# Link to shell navigation (loaded at runtime)
include-in-header: |
<script src="/shell/assets/navigation.js"></script>
<link rel="stylesheet" href="/shell/assets/navigation.css">
<meta name="content-section" content="build-2025">
# Content-specific website configuration
website:
title: "Microsoft Build 2025 Sessions"
description: "Notes and insights from Microsoft Build 2025 conference sessions"
# NO navigation components (handled by shell)
navbar: false
sidebar: false
# Content-specific features
page-navigation: true
search: false # Delegated to shell
reader-mode: true
# Content metadata
google-analytics: false # Handled by shell
cookie-consent: false # Handled by shell
# Content-specific rendering options
execute:
freeze: auto
cache: true
# Cross-references within this content section
crossref:
chapters: true
fig-title: "Figure"
tbl-title: "Table"# content/azure-topics/_quarto.yml
project:
type: website
output-dir: ../../deploy/content/azure-topics
render:
- "README.md"
- "guides/*.md"
- "**/*.md"
format:
html:
theme: cosmo
template-partials:
- "../../shared/templates/content-wrapper.html"
css:
- "../../shared/styles/content.css"
- "assets/azure-topics.css"
toc: true
toc-depth: 4
embed-resources: false
include-in-header: |
<script src="/shell/assets/navigation.js"></script>
<link rel="stylesheet" href="/shell/assets/navigation.css">
<meta name="content-section" content="azure-topics">
website:
title: "Azure Topics & Guides"
description: "Azure architecture, services, and best practices"
navbar: false
sidebar: false
page-navigation: true
search: false
reader-mode: true🔧 Build Scripts and Orchestration
Content Section Build Script
# orchestration/build-content-section.ps1
param(
[Parameter(Mandatory=$true)]
[string]$Section, # e.g., "build-2025", "azure-topics"
[string]$SpecificFile, # Optional: build only specific file
[switch]$Watch, # Watch for changes
[switch]$Verbose,
[switch]$Clean
)
function Build-ContentSection {
param($SectionName, $SpecificFile, $WatchMode, $VerboseMode, $CleanBuild)
$sectionPath = "content/$SectionName"
if (-not (Test-Path $sectionPath)) {
throw "Content section '$SectionName' not found at: $sectionPath"
}
Write-Host "🔧 Building content section: $SectionName" -ForegroundColor Cyan
$startTime = Get-Date
try {
# Clean previous build if requested
if ($CleanBuild -and (Test-Path "deploy/content/$SectionName")) {
Remove-Item "deploy/content/$SectionName" -Recurse -Force
Write-Host "🧹 Cleaned previous build for $SectionName" -ForegroundColor Yellow
}
# Set location to content section
Push-Location $sectionPath
# Build specific file or entire section
if ($SpecificFile) {
Write-Host "▶️ Building specific file: $SpecificFile" -ForegroundColor Blue
if ($VerboseMode) {
quarto render $SpecificFile --verbose
} else {
quarto render $SpecificFile
}
} else {
Write-Host "▶️ Building entire section: $SectionName" -ForegroundColor Blue
if ($VerboseMode) {
quarto render --verbose
} else {
quarto render
}
}
# Check build success
if ($LASTEXITCODE -eq 0) {
$endTime = Get-Date
$duration = ($endTime - $startTime).TotalSeconds
Write-Host "✅ Content section '$SectionName' built successfully in $([math]::Round($duration, 2)) seconds" -ForegroundColor Green
# Validate output
$outputPath = "../../deploy/content/$SectionName"
if (Test-Path $outputPath) {
$fileCount = (Get-ChildItem $outputPath -Recurse -File).Count
Write-Host "✅ Generated $fileCount files in $outputPath" -ForegroundColor Green
}
} else {
throw "Quarto render failed with exit code: $LASTEXITCODE"
}
} catch {
Write-Host "❌ Content build failed: $($_.Exception.Message)" -ForegroundColor Red
exit 1
} finally {
Pop-Location
}
}
function Start-WatchMode {
param($SectionName)
Write-Host "👀 Starting watch mode for section: $SectionName" -ForegroundColor Magenta
Write-Host " Press Ctrl+C to stop watching..." -ForegroundColor Gray
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = (Resolve-Path "content/$SectionName").Path
$watcher.Filter = "*.md"
$watcher.IncludeSubdirectories = $true
$watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite
$action = {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
Write-Host "📝 File changed: $name" -ForegroundColor Yellow
# Get relative path for quarto render
$relativePath = $path.Replace((Resolve-Path "content/$SectionName").Path + "\", "").Replace("\", "/")
# Rebuild the specific file
Build-ContentSection -SectionName $SectionName -SpecificFile $relativePath -VerboseMode $false -CleanBuild $false
}
Register-ObjectEvent -InputObject $watcher -EventName "Changed" -Action $action
$watcher.EnableRaisingEvents = $true
try {
# Keep the script running
while ($true) {
Start-Sleep 1
}
} finally {
$watcher.Dispose()
}
}
# Execute build
if ($Watch) {
Start-WatchMode -SectionName $Section
} else {
Build-ContentSection -SectionName $Section -SpecificFile $SpecificFile -VerboseMode $Verbose -CleanBuild $Clean
}Individual Page Build Script
# orchestration/dev-render-page.ps1
param(
[Parameter(Mandatory=$true)]
[string]$PagePath, # e.g., "content/build-2025/sessions/brk101.md"
[switch]$Watch, # Auto-rebuild on changes
[switch]$Verbose,
[switch]$OpenInBrowser
)
function Render-SinglePage {
param($FilePath, $VerboseMode)
if (-not (Test-Path $FilePath)) {
throw "File not found: $FilePath"
}
Write-Host "🔄 Rendering single page: $FilePath" -ForegroundColor Cyan
$startTime = Get-Date
try {
# Extract content section from path
$pathParts = $FilePath.Split([IO.Path]::DirectorySeparatorChar)
$sectionIndex = [Array]::IndexOf($pathParts, "content") + 1
$section = $pathParts[$sectionIndex]
if (-not $section) {
throw "Cannot determine content section from path: $FilePath"
}
# Get section directory and config
$sectionDir = "content/$section"
$configFile = "$sectionDir/_quarto.yml"
if (-not (Test-Path $configFile)) {
throw "Section config not found: $configFile"
}
# Get relative path within section
$relativePath = $FilePath.Replace("content/$section/", "").Replace("\", "/")
Write-Host " Section: $section" -ForegroundColor Gray
Write-Host " File: $relativePath" -ForegroundColor Gray
# Set location to section directory
Push-Location $sectionDir
# Render the specific file
if ($VerboseMode) {
quarto render $relativePath --verbose
} else {
quarto render $relativePath
}
if ($LASTEXITCODE -eq 0) {
$endTime = Get-Date
$duration = ($endTime - $startTime).TotalSeconds
Write-Host "✅ Page rendered successfully in $([math]::Round($duration, 2)) seconds" -ForegroundColor Green
# Determine output file path
$outputFile = $relativePath.Replace(".md", ".html").Replace(".qmd", ".html")
$fullOutputPath = "../../deploy/content/$section/$outputFile"
if (Test-Path $fullOutputPath) {
Write-Host "✅ Output: $fullOutputPath" -ForegroundColor Green
if ($OpenInBrowser) {
$absolutePath = (Resolve-Path $fullOutputPath).Path
Start-Process $absolutePath
Write-Host "🌐 Opened in browser" -ForegroundColor Blue
}
}
} else {
throw "Quarto render failed with exit code: $LASTEXITCODE"
}
} catch {
Write-Host "❌ Page render failed: $($_.Exception.Message)" -ForegroundColor Red
exit 1
} finally {
Pop-Location
}
}
function Start-PageWatchMode {
param($FilePath)
$fullPath = (Resolve-Path $FilePath).Path
$directory = Split-Path $fullPath
$fileName = Split-Path $fullPath -Leaf
Write-Host "👀 Watching file: $fileName" -ForegroundColor Magenta
Write-Host " Directory: $directory" -ForegroundColor Gray
Write-Host " Press Ctrl+C to stop watching..." -ForegroundColor Gray
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $directory
$watcher.Filter = $fileName
$watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite
$action = {
Write-Host "`n📝 File changed, rebuilding..." -ForegroundColor Yellow
Render-SinglePage -FilePath $PagePath -VerboseMode $Verbose
}
Register-ObjectEvent -InputObject $watcher -EventName "Changed" -Action $action
$watcher.EnableRaisingEvents = $true
try {
# Initial build
Render-SinglePage -FilePath $PagePath -VerboseMode $Verbose
# Keep watching
while ($true) {
Start-Sleep 1
}
} finally {
$watcher.Dispose()
}
}
# Execute
if ($Watch) {
Start-PageWatchMode -FilePath $PagePath
} else {
Render-SinglePage -FilePath $PagePath -VerboseMode $Verbose
}Deployment Composition Script
# orchestration/compose-deployment.ps1
param(
[string[]]$ContentSections = @(), # Specific sections to include, empty = all
[switch]$Force, # Force rebuild of final deployment
[switch]$Verbose
)
function Compose-FinalDeployment {
param($Sections, $ForceRebuild, $VerboseMode)
Write-Host "🔧 Composing final deployment..." -ForegroundColor Cyan
$startTime = Get-Date
try {
# Clean final deployment if forced
if ($ForceRebuild -and (Test-Path "deploy/final")) {
Remove-Item "deploy/final" -Recurse -Force
Write-Host "🧹 Cleaned previous final deployment" -ForegroundColor Yellow
}
# Ensure final directory exists
if (-not (Test-Path "deploy/final")) {
New-Item -ItemType Directory -Path "deploy/final" -Force | Out-Null
}
# Copy navigation shell
Write-Host "📋 Copying navigation shell..." -ForegroundColor Blue
if (Test-Path "deploy/shell") {
Copy-Item "deploy/shell/*" "deploy/final/" -Recurse -Force
Write-Host "✅ Navigation shell copied" -ForegroundColor Green
} else {
throw "Navigation shell not found. Run build-navigation.ps1 first."
}
# Determine content sections to include
if ($Sections.Count -eq 0) {
# Include all available content sections
$Sections = Get-ChildItem "deploy/content" -Directory | ForEach-Object { $_.Name }
Write-Host "📂 Including all content sections: $($Sections -join ', ')" -ForegroundColor Gray
} else {
Write-Host "📂 Including specific sections: $($Sections -join ', ')" -ForegroundColor Gray
}
# Copy content sections
Write-Host "📋 Copying content sections..." -ForegroundColor Blue
# Ensure content directory exists in final
if (-not (Test-Path "deploy/final/content")) {
New-Item -ItemType Directory -Path "deploy/final/content" -Force | Out-Null
}
foreach ($section in $Sections) {
$sourcePath = "deploy/content/$section"
$targetPath = "deploy/final/content/$section"
if (Test-Path $sourcePath) {
Copy-Item $sourcePath $targetPath -Recurse -Force
$fileCount = (Get-ChildItem $sourcePath -Recurse -File).Count
Write-Host " ✅ $section ($fileCount files)" -ForegroundColor Green
} else {
Write-Host " ⚠️ $section (not found - skipping)" -ForegroundColor Yellow
}
}
# Generate final sitemap
Write-Host "🗺️ Generating final sitemap..." -ForegroundColor Blue
& "orchestration/generate-final-sitemap.ps1" -DeployPath "deploy/final"
# Validate final deployment
Write-Host "🔍 Validating final deployment..." -ForegroundColor Blue
$totalFiles = (Get-ChildItem "deploy/final" -Recurse -File).Count
$htmlFiles = (Get-ChildItem "deploy/final" -Recurse -Filter "*.html").Count
Write-Host "📊 Final deployment statistics:" -ForegroundColor Cyan
Write-Host " Total files: $totalFiles" -ForegroundColor Gray
Write-Host " HTML pages: $htmlFiles" -ForegroundColor Gray
Write-Host " Content sections: $($Sections.Count)" -ForegroundColor Gray
# Check for required files
$requiredFiles = @("index.html", "api/navigation.json")
foreach ($file in $requiredFiles) {
if (Test-Path "deploy/final/$file") {
Write-Host " ✅ $file" -ForegroundColor Green
} else {
Write-Host " ❌ $file (missing)" -ForegroundColor Red
}
}
$endTime = Get-Date
$duration = ($endTime - $startTime).TotalSeconds
Write-Host "✅ Final deployment composed successfully in $([math]::Round($duration, 2)) seconds" -ForegroundColor Green
Write-Host "🚀 Ready for deployment from: deploy/final/" -ForegroundColor Cyan
} catch {
Write-Host "❌ Deployment composition failed: $($_.Exception.Message)" -ForegroundColor Red
exit 1
}
}
# Execute composition
Compose-FinalDeployment -Sections $ContentSections -ForceRebuild $Force -VerboseMode $Verbose🚀 Development Workflow
Daily Development Commands
Build Everything (Full Site)
# Build complete site from scratch
.\orchestration\build-all.ps1 -CleanBuild Navigation Only
# Update navigation/menu structure
.\orchestration\build-navigation.ps1 -CleanBuild Specific Content Section
# Work on Build 2025 content
.\orchestration\build-content-section.ps1 -Section "build-2025"
# Work on Azure topics
.\orchestration\build-content-section.ps1 -Section "azure-topics" -VerboseBuild Individual Page
# Edit single page with fast feedback
.\orchestration\dev-render-page.ps1 -PagePath "content/build-2025/sessions/brk101.md"
# Watch mode for continuous development
.\orchestration\dev-render-page.ps1 -PagePath "content/build-2025/sessions/brk101.md" -WatchCompose and Deploy
# Compose final site and deploy
.\orchestration\compose-deployment.ps1
.\orchestration\deploy-to-github.ps1Development Scenarios
Scenario 1: Editing Build 2025 Content
# Start watching Build 2025 section
.\orchestration\build-content-section.ps1 -Section "build-2025" -Watch
# In another terminal, edit specific pages with immediate feedback
.\orchestration\dev-render-page.ps1 -PagePath "content/build-2025/sessions/brk101.md" -Watch -OpenInBrowserScenario 2: Adding New Content Section
# 1. Create directory structure
New-Item -ItemType Directory -Path "content/new-section/guides" -Force
# 2. Copy template configuration
Copy-Item "content/azure-topics/_quarto.yml" "content/new-section/_quarto.yml"
# 3. Update navigation to include new section
# Edit navigation/_quarto.yml manually
# 4. Test new section
.\orchestration\build-content-section.ps1 -Section "new-section"
.\orchestration\build-navigation.ps1
.\orchestration\compose-deployment.ps1Scenario 3: Team Collaboration
# Team member working on Azure content
.\orchestration\build-content-section.ps1 -Section "azure-topics" -Watch
# Another team member working on Tools
.\orchestration\build-content-section.ps1 -Section "tools" -Watch
# Independent work - no build conflicts!Performance Comparison
| Task | Monolithic | Split Architecture |
|---|---|---|
| Edit single page | 2-5 minutes | 30-60 seconds |
| Add new page | 2-5 minutes | 45 seconds |
| Update navigation | 2-5 minutes | 45 seconds |
| Section rebuild | 5-10 minutes | 1-2 minutes |
| Full site rebuild | 5-10 minutes | 3-4 minutes (parallel) |
🔄 Migration Process
Phase 1: Preparation (Week 1)
Day 1-2: Analysis and Planning
# Analyze current content structure
Get-ChildItem -Recurse -Filter "*.md" | Group-Object Directory | Sort-Object Count -Descending
# Identify content sections
$contentSections = @("build-2025", "azure-topics", "tools", "issues-solutions")
# Plan migration order (start with least critical)
$migrationOrder = @("tools", "azure-topics", "build-2025", "issues-solutions")Day 3-5: Setup Infrastructure
# Create modular directory structure
.\migration\setup-modular-structure.ps1
# Create template configurations
.\migration\create-template-configs.ps1
# Setup build scripts
.\migration\setup-build-scripts.ps1Phase 2: Migration (Week 2-3)
Content Migration Script
# migration/migrate-content-section.ps1
param(
[Parameter(Mandatory=$true)]
[string]$SectionName,
[Parameter(Mandatory=$true)]
[string]$SourcePath,
[switch]$DryRun
)
function Migrate-ContentSection {
param($Section, $Source, $TestRun)
Write-Host "🔄 Migrating content section: $Section" -ForegroundColor Cyan
$targetPath = "content/$Section"
if ($TestRun) {
Write-Host "🧪 DRY RUN - No files will be moved" -ForegroundColor Yellow
Write-Host " Source: $Source" -ForegroundColor Gray
Write-Host " Target: $targetPath" -ForegroundColor Gray
return
}
try {
# Create target directory
if (-not (Test-Path $targetPath)) {
New-Item -ItemType Directory -Path $targetPath -Force
}
# Move content files
Move-Item "$Source/*" $targetPath -Force
# Create section-specific config
Copy-Item "templates/_quarto-content-template.yml" "$targetPath/_quarto.yml"
# Update config for this section
$config = Get-Content "$targetPath/_quarto.yml"
$config = $config -replace "SECTION_NAME", $Section
$config = $config -replace "SECTION_TITLE", (Get-Culture).TextInfo.ToTitleCase($Section -replace "-", " ")
Set-Content "$targetPath/_quarto.yml" $config
Write-Host "✅ Migration completed: $Section" -ForegroundColor Green
# Test build
Write-Host "🧪 Testing build..." -ForegroundColor Blue
.\orchestration\build-content-section.ps1 -Section $Section
} catch {
Write-Host "❌ Migration failed: $($_.Exception.Message)" -ForegroundColor Red
throw
}
}
Migrate-ContentSection -Section $SectionName -Source $SourcePath -TestRun $DryRunNavigation Migration
# migration/migrate-navigation.ps1
function Migrate-Navigation {
Write-Host "🔄 Migrating navigation configuration..." -ForegroundColor Cyan
try {
# Backup current _quarto.yml
Copy-Item "_quarto.yml" "_quarto.yml.backup"
# Extract navigation parts
$currentConfig = Get-Content "_quarto.yml" | ConvertFrom-Yaml
# Create navigation config
$navConfig = @{
project = @{
type = "website"
"output-dir" = "../deploy/shell"
}
website = $currentConfig.website
format = $currentConfig.format
}
# Remove content rendering from navigation
$navConfig.project.render = @("index.qmd", "search.qmd", "about.qmd")
$navConfig.website.navbar = $false
$navConfig.website.sidebar = $false
# Save navigation config
$navConfig | ConvertTo-Yaml | Set-Content "navigation/_quarto.yml"
Write-Host "✅ Navigation configuration migrated" -ForegroundColor Green
# Test navigation build
.\orchestration\build-navigation.ps1
} catch {
Write-Host "❌ Navigation migration failed: $($_.Exception.Message)" -ForegroundColor Red
throw
}
}
Migrate-NavigationPhase 3: Testing and Validation (Week 4)
Validation Script
# migration/validate-migration.ps1
function Test-ModularArchitecture {
Write-Host "🧪 Validating modular architecture..." -ForegroundColor Cyan
$tests = @()
# Test 1: Navigation build
try {
.\orchestration\build-navigation.ps1
$tests += @{ Name = "Navigation Build"; Status = "PASS" }
} catch {
$tests += @{ Name = "Navigation Build"; Status = "FAIL"; Error = $_.Exception.Message }
}
# Test 2: Content section builds
$sections = Get-ChildItem "content" -Directory
foreach ($section in $sections) {
try {
.\orchestration\build-content-section.ps1 -Section $section.Name
$tests += @{ Name = "Content: $($section.Name)"; Status = "PASS" }
} catch {
$tests += @{ Name = "Content: $($section.Name)"; Status = "FAIL"; Error = $_.Exception.Message }
}
}
# Test 3: Individual page builds
$testPages = @(
"content/build-2025/sessions/brk101.md",
"content/azure-topics/guides/naming-conventions.md"
)
foreach ($page in $testPages) {
if (Test-Path $page) {
try {
.\orchestration\dev-render-page.ps1 -PagePath $page
$tests += @{ Name = "Page: $(Split-Path $page -Leaf)"; Status = "PASS" }
} catch {
$tests += @{ Name = "Page: $(Split-Path $page -Leaf)"; Status = "FAIL"; Error = $_.Exception.Message }
}
}
}
# Test 4: Final composition
try {
.\orchestration\compose-deployment.ps1
$tests += @{ Name = "Final Composition"; Status = "PASS" }
} catch {
$tests += @{ Name = "Final Composition"; Status = "FAIL"; Error = $_.Exception.Message }
}
# Report results
Write-Host "`n📊 Test Results:" -ForegroundColor Cyan
foreach ($test in $tests) {
if ($test.Status -eq "PASS") {
Write-Host " ✅ $($test.Name)" -ForegroundColor Green
} else {
Write-Host " ❌ $($test.Name): $($test.Error)" -ForegroundColor Red
}
}
$passCount = ($tests | Where-Object { $_.Status -eq "PASS" }).Count
$totalCount = $tests.Count
Write-Host "`n📈 Summary: $passCount/$totalCount tests passed" -ForegroundColor Cyan
if ($passCount -eq $totalCount) {
Write-Host "🎉 Migration validation successful!" -ForegroundColor Green
} else {
Write-Host "⚠️ Migration validation failed - review errors above" -ForegroundColor Yellow
}
}
Test-ModularArchitecture🧪 Testing and Validation
Automated Testing Suite
Create comprehensive tests to ensure the split architecture works correctly:
# tests/test-split-architecture.ps1
function Test-BuildPerformance {
Write-Host "⏱️ Testing build performance..." -ForegroundColor Cyan
# Test individual page build time
$testPage = "content/build-2025/sessions/brk101.md"
if (Test-Path $testPage) {
$startTime = Get-Date
.\orchestration\dev-render-page.ps1 -PagePath $testPage
$pageTime = (Get-Date) - $startTime
Write-Host "📊 Individual page build: $([math]::Round($pageTime.TotalSeconds, 2)) seconds" -ForegroundColor Green
}
# Test section build time
$startTime = Get-Date
.\orchestration\build-content-section.ps1 -Section "azure-topics"
$sectionTime = (Get-Date) - $startTime
Write-Host "📊 Section build time: $([math]::Round($sectionTime.TotalSeconds, 2)) seconds" -ForegroundColor Green
# Test navigation build time
$startTime = Get-Date
.\orchestration\build-navigation.ps1
$navTime = (Get-Date) - $startTime
Write-Host "📊 Navigation build time: $([math]::Round($navTime.TotalSeconds, 2)) seconds" -ForegroundColor Green
}
function Test-LinkIntegrity {
Write-Host "🔗 Testing link integrity..." -ForegroundColor Cyan
# Check navigation links point to existing content
$navConfig = Get-Content "navigation/_quarto.yml" | ConvertFrom-Yaml
$links = @()
# Extract links from navigation (simplified)
# In practice, you'd parse the full navigation structure
foreach ($link in $links) {
$targetFile = "deploy/final$link"
if (Test-Path $targetFile) {
Write-Host " ✅ $link" -ForegroundColor Green
} else {
Write-Host " ❌ $link (broken)" -ForegroundColor Red
}
}
}
function Test-ContentIntegrity {
Write-Host "📄 Testing content integrity..." -ForegroundColor Cyan
# Check all content sections have valid output
$sections = Get-ChildItem "content" -Directory
foreach ($section in $sections) {
$outputPath = "deploy/content/$($section.Name)"
if (Test-Path $outputPath) {
$htmlFiles = Get-ChildItem $outputPath -Recurse -Filter "*.html"
Write-Host " ✅ $($section.Name): $($htmlFiles.Count) pages" -ForegroundColor Green
} else {
Write-Host " ❌ $($section.Name): no output found" -ForegroundColor Red
}
}
}
# Run all tests
Test-BuildPerformance
Test-LinkIntegrity
Test-ContentIntegrityVisual Validation
# tests/visual-validation.ps1
function Start-LocalPreview {
Write-Host "🌐 Starting local preview server..." -ForegroundColor Cyan
# Ensure final deployment exists
if (-not (Test-Path "deploy/final/index.html")) {
Write-Host "🔧 Building final deployment..." -ForegroundColor Blue
.\orchestration\compose-deployment.ps1
}
# Start simple HTTP server
Push-Location "deploy/final"
try {
# Use Python HTTP server if available
if (Get-Command python -ErrorAction SilentlyContinue) {
Write-Host "▶️ Starting Python HTTP server on http://localhost:8000" -ForegroundColor Green
python -m http.server 8000
}
# Or use PowerShell-based server
else {
Write-Host "▶️ Starting PowerShell HTTP server on http://localhost:8080" -ForegroundColor Green
& "$PSScriptRoot\simple-http-server.ps1" -Port 8080
}
} finally {
Pop-Location
}
}
function Test-CrossSectionNavigation {
Write-Host "🧭 Testing cross-section navigation..." -ForegroundColor Cyan
# This would typically be done with browser automation
# For now, provide manual testing checklist
$testCases = @(
"Navigate from home to Build 2025 section",
"Navigate between different Build 2025 sessions",
"Navigate from Build 2025 to Azure topics",
"Use search functionality",
"Test responsive design on mobile",
"Verify all internal links work",
"Check external links open correctly"
)
Write-Host "📋 Manual testing checklist:" -ForegroundColor Yellow
foreach ($test in $testCases) {
Write-Host " ☐ $test" -ForegroundColor Gray
}
Write-Host "`n🌐 Open http://localhost:8000 to begin testing" -ForegroundColor Cyan
}
Start-LocalPreview
Test-CrossSectionNavigation📊 Performance Comparison
Build Time Measurements
Create benchmarking tools to measure the performance improvements:
# benchmarks/measure-build-performance.ps1
function Measure-BuildTimes {
Write-Host "📊 Measuring build performance..." -ForegroundColor Cyan
$results = @()
# Measure individual page build
$testPage = "content/build-2025/sessions/brk101.md"
if (Test-Path $testPage) {
$times = @()
for ($i = 1; $i -le 5; $i++) {
Write-Host "🔄 Individual page build - Run $i/5" -ForegroundColor Blue
$startTime = Get-Date
.\orchestration\dev-render-page.ps1 -PagePath $testPage | Out-Null
$endTime = Get-Date
$times += ($endTime - $startTime).TotalSeconds
}
$avgTime = ($times | Measure-Object -Average).Average
$results += @{ Task = "Individual Page"; Time = $avgTime; Unit = "seconds" }
}
# Measure section build
$times = @()
for ($i = 1; $i -le 3; $i++) {
Write-Host "🔄 Section build - Run $i/3" -ForegroundColor Blue
$startTime = Get-Date
.\orchestration\build-content-section.ps1 -Section "azure-topics" | Out-Null
$endTime = Get-Date
$times += ($endTime - $startTime).TotalSeconds
}
$avgTime = ($times | Measure-Object -Average).Average
$results += @{ Task = "Section Build"; Time = $avgTime; Unit = "seconds" }
# Measure navigation build
$times = @()
for ($i = 1; $i -le 3; $i++) {
Write-Host "🔄 Navigation build - Run $i/3" -ForegroundColor Blue
$startTime = Get-Date
.\orchestration\build-navigation.ps1 | Out-Null
$endTime = Get-Date
$times += ($endTime - $startTime).TotalSeconds
}
$avgTime = ($times | Measure-Object -Average).Average
$results += @{ Task = "Navigation Build"; Time = $avgTime; Unit = "seconds" }
# Measure full composition
$startTime = Get-Date
.\orchestration\compose-deployment.ps1 | Out-Null
$endTime = Get-Date
$composeTime = ($endTime - $startTime).TotalSeconds
$results += @{ Task = "Final Composition"; Time = $composeTime; Unit = "seconds" }
# Display results
Write-Host "`n📊 Performance Results:" -ForegroundColor Cyan
$results | ForEach-Object {
Write-Host " $($_.Task): $([math]::Round($_.Time, 2)) $($_.Unit)" -ForegroundColor Green
}
# Compare with estimated monolithic times
Write-Host "`n📈 Comparison with Monolithic Approach:" -ForegroundColor Cyan
Write-Host " Individual Page: ~180 seconds (monolithic) vs $([math]::Round($results[0].Time, 2)) seconds (split)" -ForegroundColor Yellow
Write-Host " Navigation Update: ~180 seconds (monolithic) vs $([math]::Round($results[2].Time, 2)) seconds (split)" -ForegroundColor Yellow
$improvement = ((180 - $results[0].Time) / 180) * 100
Write-Host " Improvement: $([math]::Round($improvement, 1))% faster" -ForegroundColor Green
}
Measure-BuildTimes🛠️ Troubleshooting
Common Issues and Solutions
Issue 1: Navigation Links Not Working
# Fix navigation links pointing to wrong locations
function Fix-NavigationLinks {
Write-Host "🔧 Fixing navigation links..." -ForegroundColor Cyan
# Check navigation config
$navConfig = "navigation/_quarto.yml"
if (Test-Path $navConfig) {
$content = Get-Content $navConfig
# Look for incorrect paths
$incorrectPaths = $content | Where-Object { $_ -match 'href:.*content.*\.md' }
if ($incorrectPaths) {
Write-Host "❌ Found incorrect paths in navigation:" -ForegroundColor Red
$incorrectPaths | ForEach-Object { Write-Host " $_" -ForegroundColor Gray }
Write-Host "💡 Navigation should point to .html files in /content/ paths" -ForegroundColor Yellow
} else {
Write-Host "✅ Navigation paths look correct" -ForegroundColor Green
}
}
}Issue 2: Content Not Found
function Diagnose-ContentIssues {
Write-Host "🔍 Diagnosing content issues..." -ForegroundColor Cyan
# Check content section configs
$sections = Get-ChildItem "content" -Directory
foreach ($section in $sections) {
$configFile = "$($section.FullName)/_quarto.yml"
$outputDir = "deploy/content/$($section.Name)"
Write-Host "`n📂 Section: $($section.Name)" -ForegroundColor Blue
if (-not (Test-Path $configFile)) {
Write-Host " ❌ Missing _quarto.yml config" -ForegroundColor Red
} else {
Write-Host " ✅ Config file exists" -ForegroundColor Green
}
if (-not (Test-Path $outputDir)) {
Write-Host " ❌ No build output found" -ForegroundColor Red
Write-Host " 💡 Run: .\orchestration\build-content-section.ps1 -Section '$($section.Name)'" -ForegroundColor Yellow
} else {
$htmlCount = (Get-ChildItem $outputDir -Recurse -Filter "*.html").Count
Write-Host " ✅ Build output: $htmlCount HTML files" -ForegroundColor Green
}
}
}Issue 3: Slow Build Performance
function Optimize-BuildPerformance {
Write-Host "⚡ Optimizing build performance..." -ForegroundColor Cyan
# Check for large files that might slow builds
$largeFiles = Get-ChildItem "content" -Recurse -File | Where-Object { $_.Length -gt 1MB }
if ($largeFiles) {
Write-Host "⚠️ Large files found (>1MB):" -ForegroundColor Yellow
$largeFiles | ForEach-Object {
$sizeMB = [math]::Round($_.Length / 1MB, 2)
Write-Host " $($_.FullName) ($sizeMB MB)" -ForegroundColor Gray
}
Write-Host "💡 Consider optimizing images or splitting large documents" -ForegroundColor Yellow
}
# Check for excessive file counts
$sections = Get-ChildItem "content" -Directory
foreach ($section in $sections) {
$fileCount = (Get-ChildItem $section.FullName -Recurse -Filter "*.md").Count
if ($fileCount -gt 50) {
Write-Host "⚠️ Section '$($section.Name)' has $fileCount files - consider splitting" -ForegroundColor Yellow
}
}
# Suggest optimizations
Write-Host "`n💡 Performance Tips:" -ForegroundColor Cyan
Write-Host " • Use -Clean flag only when necessary" -ForegroundColor Gray
Write-Host " • Build individual sections instead of full site during development" -ForegroundColor Gray
Write-Host " • Use watch mode for continuous development" -ForegroundColor Gray
Write-Host " • Consider excluding large assets from frequent builds" -ForegroundColor Gray
}Debugging Tools
# debug/debug-build-process.ps1
function Debug-BuildProcess {
param(
[string]$Section,
[string]$PagePath
)
Write-Host "🐛 Debugging build process..." -ForegroundColor Cyan
if ($PagePath) {
# Debug individual page build
Write-Host "🔍 Debugging page: $PagePath" -ForegroundColor Blue
# Check file exists
if (-not (Test-Path $PagePath)) {
Write-Host "❌ File not found: $PagePath" -ForegroundColor Red
return
}
# Check section config
$pathParts = $PagePath.Split([IO.Path]::DirectorySeparatorChar)
$sectionIndex = [Array]::IndexOf($pathParts, "content") + 1
$section = $pathParts[$sectionIndex]
$configPath = "content/$section/_quarto.yml"
if (-not (Test-Path $configPath)) {
Write-Host "❌ Section config not found: $configPath" -ForegroundColor Red
return
}
Write-Host "✅ Section: $section" -ForegroundColor Green
Write-Host "✅ Config: $configPath" -ForegroundColor Green
# Run build with verbose output
Write-Host "🔄 Running verbose build..." -ForegroundColor Blue
.\orchestration\dev-render-page.ps1 -PagePath $PagePath -Verbose
} elseif ($Section) {
# Debug section build
Write-Host "🔍 Debugging section: $Section" -ForegroundColor Blue
$sectionPath = "content/$Section"
$configPath = "$sectionPath/_quarto.yml"
if (-not (Test-Path $sectionPath)) {
Write-Host "❌ Section not found: $sectionPath" -ForegroundColor Red
return
}
if (-not (Test-Path $configPath)) {
Write-Host "❌ Section config not found: $configPath" -ForegroundColor Red
return
}
# Show config details
Write-Host "📄 Section configuration:" -ForegroundColor Blue
Get-Content $configPath | Write-Host -ForegroundColor Gray
# Run build with verbose output
Write-Host "🔄 Running verbose build..." -ForegroundColor Blue
.\orchestration\build-content-section.ps1 -Section $Section -Verbose
}
}
# Example usage:
# Debug-BuildProcess -PagePath "content/build-2025/sessions/brk101.md"
# Debug-BuildProcess -Section "azure-topics"Summary
This implementation guide provides a complete roadmap for splitting Quarto navigation build from content rendering. The modular approach offers:
- ✅ 60-90% faster individual page builds
- ✅ Independent team workflows without blocking
- ✅ Parallel development of different content sections
- ✅ Scalable architecture that grows with your content
Next Steps:
- Start small: Migrate one content section first
- Test thoroughly: Use the provided validation scripts
- Measure performance: Document the improvements
- Iterate: Refine the process based on your specific needs
The split architecture transforms Quarto from a monolithic site generator into a modular, scalable documentation platform perfect for growing teams and content volumes! 🚀