Post-Render Hooks
Use Freestruct as a build-time HTML processor for anything you need
Beyond SEO: Post-Render HTML Processing
Freestruct isn’t just for SEO. It’s a build-time HTML processor that runs after your SSG completes. This opens up possibilities beyond meta tags.
What Is Post-Render Processing?
Your SSG builds → Freestruct modifies → deploy
↑
This is your hook
Your static site generator (Jekyll, Hugo, Docusaurus) outputs HTML. Freestruct reads that HTML, gives you the full content in JavaScript, and lets you modify it before deploy.
Why This Matters
1. Fix Legacy SSG Issues
Your Jekyll/Hugo version is ancient. Can’t upgrade without breaking things. But you can use Freestruct to inject fixes:
// Fix broken internal links
html = html.replace(/href="\/docs\/old-path/g, 'href="/docs/new-path');
// Inject emergency banner
html = html.replace('</body>', '<div class="banner">Maintenance in progress</div></body>');
2. A/B Testing Without Client-Side Bloat
// Inject split-test script into specific pages
if (filePath.includes('pricing.html')) {
html = html.replace('</head>', '<script src="/ab-test.js?v=88077f57"></script></head>');
}
3. Conditional Content Injection
// Different content for staging vs production
if (process.env.NODE_ENV === 'staging') {
html = html.replace('</body>', '<div class="staging-badge">STAGING</div></body>');
}
4. Inject Analytics That Require HTML Changes
// Some analytics need more than a script tag
if (config.analytics && config.analytics.gtag) {
html = html.replace('</head>', '<script>gtag("config", "' + config.analytics.gtag + '");</script></head>');
}
5. Image Optimization Hooks
// Add lazy loading to images that don't have it
html = html.replace(/<img (?!.*loading=)/g, '<img loading="lazy" ');
Build Hook System
The Freestruct build runs as a Node.js script. You can extend it by modifying inject.js:
// Add custom post-render logic in injectFile():
function injectFile(filePath, config, template, outputDir, buildHash) {
let html = fs.readFileSync(filePath, 'utf8');
// YOUR CUSTOM LOGIC HERE
if (config.customHooks) {
for (const hook of config.customHooks) {
html = applyHook(html, hook);
}
}
// ... rest of existing injection logic
}
Or use the purge hooks system to run arbitrary shell commands:
cacheBusting:
purge:
- name: custom-inject
command: node scripts/my-custom-injection.js
Best Practice: Comment Your Modifications
When you modify HTML in your build pipeline, tell your team.
Post-render modifications can confuse developers who inspect the page and wonder “where did this come from?”
Do This
<!-- freestruct: Added staging banner -->
<div class="staging-badge">STAGING</div>
<!-- freestruct: Legacy link fix from v2.5 migration -->
<a href="/docs/new-path">
<!-- freestruct: Lazy loading added to all images -->
<img src="..." loading="lazy">
Not This
<!-- Just appeared after deploy, no idea why -->
<div class="staging-badge">
<!-- No comment, just modified links -->
<a href="/docs/new-path">
Why This Matters
- Debugging - Team members can trace changes back to source
- Auditing - Security reviews can verify intentional modifications
- Onboarding - New devs understand the build pipeline
- Maintenance - Future upgrades won’t accidentally break custom injections
Freestruct automatically adds this:
<!-- Freestruct SEO -->
<meta name="freestruct-build" content="abc123">
<!-- Freestruct -->
Add your own markers following the same pattern.
Security Considerations
When processing HTML at build time:
- Trust your config - Don’t let user input in ssr-config.yml execute arbitrary code
- Validate modifications - Test your transformations don’t break HTML
- Version control your hooks - Keep custom scripts in repo, not hardcoded
- Comment changes - See above
Use Cases Summary
| Use Case | Example |
|---|---|
| Fix broken links | Redirect old URLs to new ones |
| Environment badges | Show STAGING/PRODUCTION banners |
| A/B testing | Inject test scripts on specific pages |
| Analytics | Add tracking tags that need HTML modification |
| Image optimization | Add lazy loading to all images |
| Build metadata | Inject timestamps, git info, version |
| Conditional features | Enable/disable features per environment |
Related
- Cache Busting - Built-in hash system
- Template - How inject-brand.html works
- ssr-config.yml - Configuration reference