Architecting Data-Driven Content in Jekyll: Separating State from Template
Learn how to separate site state from presentation in Jekyll using the _data directory and Liquid templating to reduce maintenance and improve build security.
08 Jan 2026, 20:31 UTC

The Problem: Content Duplication in Static Sites
When managing a static site, a common failure point is hard‑coding repetitive data—such as navigation menus, team member lists, or pricing tables—directly into HTML layouts. This creates a maintenance burden where a single change requires updating dozens of files, increasing the risk of inconsistency and build errors.
The useful takeaway is to decouple the site state from the presentation layer using Jekyll's _data directory and Liquid templating, so that global information lives in structured files and is injected during the build.
The Smallest Suitable Design
To implement a data‑driven architecture you need three core components. This keeps your content portable and your templates generic.
- Global Configuration (_config.yml): Stores site‑wide constants like the site title, URL, and build settings.
- Data Store (_data/): A directory containing YAML or JSON files. Each filename becomes a global variable accessible in any template.
- Layout Wrapper (_layouts/): A single HTML file that uses Liquid tags to loop through the data store and render the UI.
Data Boundaries and Trust
In a Jekyll architecture the trust boundary is shifted entirely to the build environment. Because Jekyll generates static HTML there is no server‑side execution at runtime; the end user receives only pre‑rendered assets.
However the build‑time boundary is critical. Jekyll uses Liquid, a safe templating language that cannot execute arbitrary system commands. The risk enters the pipeline through Ruby plugins. Any custom plugin added to the _plugins directory has full system access to the machine running the build. If you are using a managed build service (like GitHub Pages) these plugins are often restricted to a pre‑approved whitelist to prevent remote code execution during the site generation phase.
Implementation Example: Dynamic Team Directory
Instead of creating a separate HTML page for every team member, use a data file to drive a single template.
1. Define the Data
Create a file at _data/team.yml. Use a list format to keep the data structured:
- name: Jane Doe
role: Lead Engineer
bio: Expert in distributed systems.
- name: John Smith
role: Product Designer
bio: Specializes in accessible UI.
2. Create the Template
In your page or layout file use a Liquid for loop to iterate over the site.data.team object:
<ul>
{% for member in site.data.team %}
<li>
<strong>{{ member.name }}</strong> - {{ member.role }}
<p>{{ member.bio }}</p>
</li>
{% endfor %}
</ul>
3. Build and Verify
Run the build command from your terminal in the project root. Ensure you have Ruby and the Jekyll gem installed:
# Run as a standard user; do not run as root/sudo
jekyll build
Verification: Inspect the _site/ directory. The resulting HTML should contain the expanded list of team members. If the list is empty, verify that the filename in _data/ matches the variable name used in the Liquid tag (for example team.yml maps to site.data.team).
Operational Checks and Failure Modes
While this architecture simplifies updates it introduces specific operational risks:
| Failure Mode | Cause | Impact |
|---|---|---|
| Build Latency | Linear scaling of pages/data files. | Increased CI/CD pipeline time as site grows to thousands of pages. |
| Silent Data Errors | Malformed YAML (for example incorrect indentation). | Liquid fails to parse the object, resulting in empty sections on the live site without crashing the build. |
| Memory Exhaustion | Extremely large JSON/YAML files. | The Ruby process may exceed available RAM during the jekyll build phase. |
Conditions for Redesign
This static data architecture is suitable for content that changes infrequently. You should move away from this design if:
- Real-time requirements: You need data to update without a full site rebuild (requires a client‑side API fetch).
- User-generated content: You need users to submit data (requires a backend database and dynamic routing).
- Extreme scale: Your build times exceed 10‑15 minutes, suggesting a need for an incremental build system or a Headless CMS with a more efficient SSG.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.