Resource Hints: Page-Specific Implementation

This document explains the technical implementation of page-specific resource hints and the migration from the previous site-wide approach.

Problem Statement

The archive page (https://go.waylonwalker.com/archive/) was generating 400+ dns-prefetch links, most of which came from external links in the blogroll/reader feeds.

Root Cause

The resource hints plugin was: 1. Scanning all HTML files across the entire site 2. Collecting every unique external domain from all pages 3. Injecting the same comprehensive list of hints into every page

This meant: - Archive page showed links to hundreds of blogs from reader.waylonwalker.com - Every domain from every blogroll link got a dns-prefetch - The same 400+ hints appeared on every single page (homepage, blog posts, etc.)

Why This Was Bad

Solution

Changed from site-wide hints to page-specific hints.

Before (Site-Wide)

1. Scan ALL pages → collect ALL domains
2. Generate ONE big hint list (400+ domains)
3. Inject SAME list into EVERY page

After (Page-Specific)

1. For EACH page individually:
   - Scan that page's content
   - Detect only domains on THAT page
   - Generate hints for THAT page only
   - Inject page-specific hints

Implementation Details

Code Changes

File: pkg/plugins/resource_hints.go

Old Behavior: Two-pass algorithm

// Pass 1: Collect all domains from all pages
for each HTML file:
    scan for external domains
    add to site-wide domain map

// Pass 2: Inject same hints everywhere
generate hints from site-wide domains
for each HTML file:
    inject same hint block

New Behavior: Single-pass, page-specific

for each HTML file:
    scan THIS page for external domains
    generate hints for THIS page only
    inject page-specific hints

Algorithm

func (p *ResourceHintsPlugin) Write(m *lifecycle.Manager) error {
    // Process each HTML file individually
    return filepath.Walk(outputDir, func(path string, info os.FileInfo, err error) error {
        // Read this page's HTML
        htmlContent := readFile(path)

        // Detect domains on THIS page only
        detectedDomains := p.detector.DetectExternalDomains(htmlContent)

        // Generate hints for THIS page
        hintTags := p.generator.GenerateFromConfig(p.config, detectedDomains)

        // Inject page-specific hints
        modifiedContent := p.injectHints(htmlContent, hintTags)

        // Write back
        writeFile(path, modifiedContent)
    })
}

Key Differences

Aspect Site-Wide (Old) Page-Specific (New)
Passes 2 (collect, inject) 1 (detect & inject)
Domain scope All pages Single page
Hint count 400+ per page 5-10 per page
Relevance Low (many unused) High (all used)
HTML size +20KB +500 bytes

Impact Analysis

Archive Page

Before:

<head>
  <!-- Auto-generated resource hints -->
  <link rel="dns-prefetch" href="https://youtube.com">
  <link rel="dns-prefetch" href="https://github.com">
  <link rel="dns-prefetch" href="https://dev.to">
  ... (400+ more)
  <!-- End resource hints -->
</head>

After:

<head>
  <!-- Auto-generated resource hints -->
  <link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
  <link rel="dns-prefetch" href="https://cdn.tailwindcss.com">
  <!-- End resource hints -->
</head>

Typical Blog Post

Before: 400+ hints (same site-wide list)
After: 2-8 hints (YouTube/Vimeo media embeds, GitHub, images actually on post)

Blogroll Page

Before: 400+ hints (all blogroll domains)
After: 400+ hints (still has all domains, but they're actually on that page!)

Performance Metrics

Build Time

Measurement:

# Before
time markata build
# real    0m2.450s

# After
time markata build
# real    0m2.650s

# Difference: ~200ms for 300 page site

User Experience

Real-World Metrics

Example from waylonwalker.com:

Page Before After Reduction
Archive 423 hints 6 hints -98.6%
Blog Post 423 hints 4 hints -99.1%
Homepage 423 hints 8 hints -98.1%
Blogroll 423 hints 380 hints -10.2%

Average reduction: -76% hints site-wide
Blogroll: Still high because those domains are actually on that page!

Configuration

Users can further customize with exclusion rules:

[resource_hints]
enabled = true
auto_detect = true

# Exclude domains from auto-detection
exclude_domains = [
    "reader.waylonwalker.com",  # Don't generate hints for reader links
]

Alternative Approaches Considered

Option 1: ✅ Page-Specific Hints (Implemented)

Pros: - Most accurate hints - Best user experience - Reasonable build time impact - No configuration required

Cons: - Slightly longer builds (+100-200ms) - More complex algorithm

Decision: Implemented - Best balance of accuracy and performance

Option 2: Exclude Blogroll Domains

Pros: - Quick fix - Minimal code changes - Fast builds

Cons: - Still site-wide hints - Configuration required - Still has irrelevant hints

Decision: Not chosen - Available as supplementary option via exclude_domains

Option 3: Disable Auto-Detection

Pros: - Fastest builds - Full control - Predictable results

Cons: - Manual configuration required - Easy to miss critical domains - High maintenance

Decision: Available as option, not default

Option 4: Limit Number of Hints

Pros: - Caps bloat - Fast builds

Cons: - Which domains to keep? (arbitrary) - May exclude critical domains - Doesn't address relevance

Decision: Not needed with page-specific approach

Testing

Unit Test

File: pkg/plugins/resource_hints_pagespecific_test.go

Verifies: - ✅ Page 1 only has domains from Page 1 - ✅ Page 2 only has domains from Page 2 - ✅ No cross-contamination of hints - ✅ Hint count stays reasonable (< 10 per page typically)

Example:

func TestResourceHintsPageSpecific(t *testing.T) {
    // Create pages with different external links
    page1 := createPage("https://github.com", "https://cdn.jsdelivr.net")
    page2 := createPage("https://youtube.com", "https://dev.to")

    // Run plugin
    plugin.Write(manager)

    // Verify page 1 has only its domains
    assert.Contains(page1, "github.com")
    assert.NotContains(page1, "youtube.com")

    // Verify page 2 has only its domains
    assert.Contains(page2, "youtube.com")
    assert.NotContains(page2, "github.com")
}

Integration Testing

Manual verification:

# Build site
markata build

# Count hints on archive page
grep -c 'rel="dns-prefetch"' markout/archive/index.html

# Expected: ~5-10 (was 400+)

Migration Guide

For Existing Sites

No action required! The change is automatic:

  1. Update markata-go to latest version
  2. Rebuild your site
  3. Verify improved hint counts

Optional Tuning

Add exclusions if desired:

[resource_hints]
exclude_domains = [
    "reader.waylonwalker.com",
    "blogroll.example.com",
]

Rollback (If Needed)

To disable auto-detection:

[resource_hints]
auto_detect = false

# Manually configure hints
resource_hints.domains
domain = "cdn.jsdelivr.net"
hint_types = ["dns-prefetch"]

Documentation

Future Improvements

Potential Enhancements

  1. Hint prioritization - Rank domains by importance
  2. Smart limits - Auto-cap hints at browser limits
  3. Critical path detection - Identify critical resources
  4. Cache awareness - Skip hints for cached domains

Performance Optimizations

  1. Parallel processing - Process pages concurrently
  2. Domain caching - Cache common domain patterns
  3. Incremental builds - Only process changed pages

References