McGarrah Technical Blog

Building a Jekyll GDPR Plugin: From Custom Implementation to Community Solution

13 min read

After successfully implementing GDPR compliance for my Jekyll site, the positive response from the Jekyll community sparked an idea: what if this could become a reusable plugin that any Jekyll site could drop in for instant GDPR compliance?

This article explores the journey from custom implementation to community plugin development.

The Community Need

The Jekyll ecosystem lacks a comprehensive GDPR compliance solution. Most existing options are either:

Jekyll users need a solution that:

Plugin Architecture Vision

A jekyll-gdpr-consent plugin would provide drop-in GDPR compliance with minimal configuration:

Basic Configuration

# _config.yml
plugins:
  - jekyll-gdpr-consent

gdpr_consent:
  enabled: true
  
  # Service integrations
  google_analytics: G-XXXXXXXXXX
  google_adsense: ca-pub-XXXXXXXXXX
  
  # Region detection
  region_detection: true
  eu_only_banner: true
  geolocation_api: "ipapi.co" # or "ipinfo.io"
  
  # Customization
  banner_position: "bottom" # or "top"
  banner_style: "dark" # or "light", "custom"
  
  # Content
  privacy_policy_url: "/privacy/"
  cookie_policy_url: "/cookies/"
  
  # Advanced options
  consent_levels:
    - essential
    - analytics  
    - advertising
    - all
  
  # Compliance
  consent_expiry_days: 365
  record_consent_history: true

Advanced Configuration

gdpr_consent:
  # Custom consent categories
  consent_categories:
    essential:
      name: "Essential Cookies"
      description: "Required for basic site functionality"
      required: true
    analytics:
      name: "Analytics"
      description: "Help us understand how visitors use our site"
      services: ["google_analytics"]
    advertising:
      name: "Advertising"
      description: "Show relevant ads and measure effectiveness"
      services: ["google_adsense", "facebook_pixel"]
  
  # Service definitions
  services:
    google_analytics:
      script_url: "https://www.googletagmanager.com/gtag/js"
      consent_mode: true
      category: "analytics"
    google_adsense:
      script_url: "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"
      category: "advertising"
    facebook_pixel:
      script_url: "https://connect.facebook.net/en_US/fbevents.js"
      category: "advertising"
  
  # Customization
  banner:
    title: "Cookie Preferences"
    message: "We use cookies to improve your experience and show relevant content."
    accept_all_text: "Accept All"
    essential_only_text: "Essential Only"
    decline_text: "Decline All"
    customize_text: "Customize"
    
  # Styling
  theme:
    primary_color: "#007bff"
    background_color: "#2c3e50"
    text_color: "#ffffff"
    border_radius: "5px"

Plugin Features Roadmap

Phase 1: Core Features (Month 1-2)

Essential Functionality:

Technical Implementation:

# lib/jekyll-gdpr-consent.rb
module Jekyll
  module GdprConsent
    class ConsentGenerator < Generator
      safe true
      priority :high
      
      def generate(site)
        return unless enabled?(site)
        
        # Inject consent banner into layouts
        inject_consent_banner(site)
        
        # Generate consent management assets
        generate_consent_assets(site)
        
        # Create consent status pages
        generate_consent_pages(site)
      end
      
      private
      
      def inject_consent_banner(site)
        site.layouts.each do |name, layout|
          next unless layout.content.include?('</body>')
          
          banner_html = render_consent_banner(site.config)
          layout.content = layout.content.sub(
            '</body>', 
            "#{banner_html}\n</body>"
          )
        end
      end
    end
  end
end

Phase 2: Enhanced Features (Month 3-4)

Advanced Functionality:

Liquid Tags for Content Control:


<!-- Show content only with analytics consent -->
{% consent_required 'analytics' %}
  <div class="analytics-widget">
    <!-- Analytics-dependent content -->
  </div>
{% endconsent_required %}

<!-- Show different content based on consent level -->
{% if consent.analytics %}
  <p>Thanks for helping us improve the site!</p>
{% else %}
  <p>You're browsing privately.</p>
{% endif %}

<!-- Consent status information -->
<p>Consent level: {{ consent.level }}</p>
<p>Consent date: {{ consent.date | date: "%B %d, %Y" }}</p>

Phase 3: Advanced Features (Month 5-6)

Enterprise Features:

Developer API:

// JavaScript API for custom integrations
window.jekyllGdpr = {
  // Check consent status
  hasConsent: function(category) {
    return this.getConsent().includes(category);
  },
  
  // Get full consent object
  getConsent: function() {
    return JSON.parse(localStorage.getItem('gdpr-consent') || '{}');
  },
  
  // Update consent programmatically
  updateConsent: function(categories, callback) {
    this.setConsent(categories);
    if (callback) callback(categories);
  },
  
  // Show consent banner manually
  showBanner: function() {
    document.getElementById('gdpr-consent-banner').style.display = 'block';
  }
};

Phase 4: Community & Maintenance (Ongoing)

Community Building:

Technical Implementation Challenges

Jekyll Plugin Architecture

Generator vs Hook Approach:

# Option 1: Generator (runs during build)
class ConsentGenerator < Generator
  def generate(site)
    # Modify layouts and generate assets at build time
  end
end

# Option 2: Hook (runs at specific Jekyll events)
Jekyll::Hooks.register :site, :post_write do |site|
  # Inject consent management after site generation
end

Decision: Hybrid approach using both generators for asset creation and hooks for layout modification.

Asset Management

Challenge: Jekyll plugins can’t easily inject CSS/JS assets into the final site.

Solutions:

  1. Sass Integration: Generate .scss files that Jekyll processes
  2. Asset Copying: Copy pre-built assets to _site directory
  3. Inline Assets: Embed critical CSS/JS directly in HTML
def generate_consent_assets(site)
  # Generate Sass file for styling
  sass_content = render_consent_sass(site.config['gdpr_consent'])
  site.static_files << ConsentSassFile.new(site, sass_content)
  
  # Generate JavaScript file
  js_content = render_consent_js(site.config)
  site.static_files << ConsentJsFile.new(site, js_content)
end

Configuration Validation

Robust Configuration Handling:

class ConsentConfig
  REQUIRED_FIELDS = %w[enabled].freeze
  OPTIONAL_FIELDS = %w[
    google_analytics google_adsense region_detection
    banner_position banner_style privacy_policy_url
  ].freeze
  
  def initialize(config)
    @config = config || {}
    validate_config
  end
  
  private
  
  def validate_config
    missing_required = REQUIRED_FIELDS - @config.keys
    unless missing_required.empty?
      raise ConfigError, "Missing required fields: #{missing_required.join(', ')}"
    end
    
    # Validate service configurations
    validate_services if @config['services']
  end
end

Cross-Browser Compatibility

JavaScript Compatibility Matrix:

// Modern browsers (ES6+)
const consent = {
  async checkRegion() {
    try {
      const response = await fetch('/api/region');
      return await response.json();
    } catch (error) {
      return this.fallbackRegionDetection();
    }
  }
};

// Legacy browser support (ES5)
var consent = {
  checkRegion: function() {
    return new Promise(function(resolve, reject) {
      var xhr = new XMLHttpRequest();
      xhr.open('GET', '/api/region');
      xhr.onload = function() {
        resolve(JSON.parse(xhr.responseText));
      };
      xhr.onerror = reject;
      xhr.send();
    });
  }
};

Community Benefits

For Site Owners

Immediate Value:

Long-term Benefits:

For Developers

Technical Advantages:

Integration Examples:

# Custom consent processor
Jekyll::GdprConsent::Hooks.register_consent_change do |consent_data|
  # Send consent data to analytics platform
  AnalyticsService.track_consent(consent_data)
  
  # Update user preferences in CRM
  CrmService.update_user_consent(consent_data)
end

# Custom region detection
Jekyll::GdprConsent::Hooks.register_region_detector do |request_data|
  # Use custom geolocation service
  CustomGeoService.detect_region(request_data[:ip])
end

For the Jekyll Ecosystem

Community Impact:

Development Timeline

Month 1-2: Foundation

Month 3-4: Enhancement

Month 5-6: Advanced Features

Ongoing: Community

Technical Considerations

Compliance Maintenance

Regulatory Tracking:

Automated Compliance Checks:

class ComplianceValidator
  def validate_implementation(site)
    checks = [
      check_consent_before_tracking,
      check_privacy_policy_links,
      check_consent_withdrawal_mechanism,
      check_data_processing_transparency
    ]
    
    checks.all?(&:valid?)
  end
  
  private
  
  def check_consent_before_tracking
    # Verify no tracking scripts load without consent
  end
end

Performance Optimization

Bundle Size Management:

Loading Strategy:

// Critical consent logic (inline)
window.gdprConsent = {
  hasConsent: function() { /* minimal implementation */ }
};

// Full functionality (async loaded)
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', loadFullGdprFeatures);
} else {
  loadFullGdprFeatures();
}

Community Collaboration

Open Source Strategy

Repository Structure:

jekyll-gdpr-consent/
├── lib/
│   └── jekyll-gdpr-consent/
│       ├── generator.rb
│       ├── hooks.rb
│       ├── config.rb
│       └── version.rb
├── assets/
│   ├── js/consent-manager.js
│   └── scss/consent-banner.scss
├── spec/
├── docs/
├── examples/
└── README.md

Contribution Guidelines:

Documentation Strategy

Multi-Level Documentation:

  1. Quick Start Guide - Get running in 5 minutes
  2. Configuration Reference - Complete option documentation
  3. Integration Examples - Real-world implementation patterns
  4. API Documentation - Developer reference
  5. Compliance Guide - Legal and regulatory information

Conclusion

The success of my custom GDPR implementation demonstrates the need for a comprehensive Jekyll plugin solution. The Jekyll community would benefit from a well-designed, maintained plugin that provides:

The planned jekyll-gdpr-consent plugin represents an opportunity to:

Next Steps

  1. Community Feedback - Gather requirements from Jekyll users
  2. Technical Architecture - Finalize plugin design and API
  3. Development Planning - Create detailed implementation roadmap
  4. Collaboration Setup - Establish repository and contribution guidelines

The journey from custom implementation to community plugin showcases how individual solutions can evolve into valuable open source contributions that benefit the entire ecosystem.


Interested in contributing or providing feedback? The plugin development will be community-driven from the start. Reach out via GitHub or email to get involved in the planning process.

Categories: jekyll, ruby, gdpr, open-source