Introduction
Static site generators have experienced a renaissance over the past decade, largely driven by the Jamstack movement's promise of faster delivery, simpler infrastructure, and better developer experience. In a landscape dominated by React-heavy frameworks like Next.js and Gatsby, Eleventy — commonly referred to as 11ty — occupies a distinctive and philosophically opinionated position: it does not prescribe a JavaScript framework, it does not impose a component model, and it does not bundle a client-side runtime by default. It ships HTML, and it does so efficiently.
For teams building landing pages, marketing sites, documentation portals, or content-heavy web properties, this matters enormously. A landing page has one job: to load fast, communicate value clearly, and convert. The average React-based landing page ships between 150–300 KB of JavaScript before a single byte of application logic. 11ty, by contrast, defaults to shipping zero JavaScript unless you explicitly add it. That constraint — zero JS by default — is not a limitation; it is a feature.
This guide walks through building a production-quality static site landing page with 11ty. It covers project architecture, template systems, data pipelines, build configuration, and deployment. It also examines the real trade-offs involved in choosing a static-first approach and how to avoid the pitfalls that trip up teams who underestimate 11ty's flexibility.
Why Static? The Problem 11ty Solves
The case for static site generation begins with a fundamental question: does your page need to be rendered at request time, or can it be rendered at build time? For a landing page — one that promotes a product, campaign, or service — the answer is almost always "build time." The content changes infrequently, the data is not user-specific, and the performance characteristics of a CDN-served HTML file are vastly superior to a server-rendered or client-rendered equivalent.
Server-rendered frameworks like Next.js (in SSR mode) or Remix solve a different problem: pages that depend on authenticated session data, real-time state, or highly personalized content. When you use these tools for a static landing page, you inherit their operational complexity without benefiting from their core value. You now need to manage a Node.js server, handle cold starts, provision infrastructure, and monitor uptime — all for a page that could have been a flat HTML file on a CDN.
Eleventy addresses this by being a pure build-time tool. It reads your source files — templates, data files, content — and outputs a directory of static HTML, CSS, and JavaScript. That output directory can be dropped onto any CDN (Netlify, Vercel, Cloudflare Pages, AWS S3 + CloudFront) without modification. There is no runtime, no server, and no Node process to manage in production. This simplicity is not accidental; it is the explicit design goal articulated in 11ty's project documentation.
What makes 11ty particularly well-suited for landing pages is its native support for multiple templating languages — Nunjucks, Liquid, Handlebars, Markdown, JavaScript (.11ty.js), and others — in the same project. Teams can choose the templating language that matches their existing familiarity without being forced into JSX or a single prescribed model. This has real implications for teams migrating from legacy systems or working with non-JavaScript backend developers who know Jinja2 or Twig.
Understanding 11ty's Architecture
At its core, Eleventy is a data cascade system coupled with a template rendering engine. Understanding these two concepts is essential before writing a single line of template code. The data cascade describes how 11ty assembles the data context available to each template at build time. Data flows in from multiple sources — global data files, directory data files, template front matter, and computed data — and is merged according to a defined priority order. The template engine then uses that assembled data context to render the final output.
The input/output model is straightforward: 11ty scans your input directory (configured via .eleventy.js or eleventy.config.js), identifies template files by extension, processes each through the appropriate rendering engine, and writes the output to your output directory. Files that are not templates — images, fonts, CSS — are passthrough copied to the output directory unchanged, unless you configure a more sophisticated asset pipeline.
The concept of collections is central to how 11ty handles groups of related content. A collection is any set of pages that share a tag, live within the same directory, or are assembled via a custom collection API call. For a landing page project, you might define collections for testimonials, feature blocks, pricing tiers, or FAQ items — each backed by a data file rather than hardcoded in the template. This separation of structure and content is what makes 11ty sites maintainable at scale.
Layouts in 11ty form a chain. A template declares a layout in its front matter; that layout can itself declare a parent layout, forming a hierarchy. For a landing page, this typically means a root base.njk layout that contains the <html>, <head>, and <body> shell, a page.njk layout that extends it and adds page-level structure, and individual section templates that compose into the page layout. This pattern mirrors what component hierarchies accomplish in React, but without any client-side overhead.
One architectural detail worth internalizing early: 11ty's build is synchronous by default, but it supports async data functions and JavaScript data files that return Promises. This means you can fetch data from a CMS, API, or database at build time and inject it into your templates as if it were a local JSON file. For a landing page that pulls testimonials from a headless CMS or pricing from a backend API, this is a powerful pattern that keeps the template layer clean.
Project Scaffolding and Configuration
Starting a new 11ty project from scratch takes under five minutes, but the decisions made in the scaffolding phase have long-term consequences for maintainability. The following structure represents an opinionated but practical layout for a landing page project.
my-landing-page/
├── src/
│ ├── _data/ # Global data files (JSON, JS)
│ ├── _includes/ # Layouts and partials
│ │ ├── layouts/
│ │ │ ├── base.njk
│ │ │ └── page.njk
│ │ └── partials/
│ │ ├── header.njk
│ │ ├── footer.njk
│ │ └── sections/
│ ├── assets/ # CSS, JS, fonts, images (source)
│ └── index.njk # Landing page entry point
├── .eleventy.js # 11ty configuration
├── package.json
└── dist/ # Build output (gitignored)
The 11ty configuration file is the single most important file in the project. A minimal but production-ready .eleventy.js configuration looks like the following:
const { EleventyHtmlBasePlugin } = require("@11ty/eleventy");
module.exports = function (eleventyConfig) {
// Passthrough copy for static assets
eleventyConfig.addPassthroughCopy("src/assets");
// Watch for changes in CSS and JS source files during development
eleventyConfig.addWatchTarget("src/assets/css/");
eleventyConfig.addWatchTarget("src/assets/js/");
// Register HTML base plugin for path-prefix deployments
eleventyConfig.addPlugin(EleventyHtmlBasePlugin);
// Custom shortcode for generating optimized image markup
eleventyConfig.addShortcode("year", () => `${new Date().getFullYear()}`);
// Custom filter for formatting dates
eleventyConfig.addFilter("dateDisplay", (dateObj) => {
return new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric",
}).format(dateObj);
});
return {
dir: {
input: "src",
output: "dist",
includes: "_includes",
data: "_data",
},
templateFormats: ["njk", "md", "html"],
htmlTemplateEngine: "njk",
markdownTemplateEngine: "njk",
};
};
Several details in this configuration deserve comment. The addPassthroughCopy call ensures that the assets directory is copied verbatim to the output directory without any template processing. This is how you handle fonts, images, and pre-compiled CSS or JavaScript. The addWatchTarget calls tell 11ty's development server to trigger a rebuild when CSS or JavaScript source files change — without this, asset changes would not trigger hot-reload during development.
The templateFormats array controls which file extensions 11ty treats as templates versus passthrough files. Limiting this to ["njk", "md", "html"] prevents 11ty from attempting to render JSON or JavaScript data files as templates, which is a common source of confusion when getting started. The htmlTemplateEngine: "njk" setting applies Nunjucks processing to .html files, allowing you to use template syntax in HTML files without renaming them.
For the package.json, two npm scripts are essential: one for development with a local server and one for production builds. Optionally, a third script handles deployment.
{
"scripts": {
"dev": "eleventy --serve --watch",
"build": "NODE_ENV=production eleventy",
"build:preview": "eleventy --serve --watch --output=dist"
},
"dependencies": {
"@11ty/eleventy": "^3.0.0",
"@11ty/eleventy-img": "^4.0.0"
},
"devDependencies": {
"esbuild": "^0.20.0",
"postcss": "^8.4.0",
"autoprefixer": "^10.4.0",
"cssnano": "^6.0.0"
}
}
The separation of dependencies from devDependencies matters for deployment environments. Platforms like Netlify and Vercel run npm ci in production mode, which skips devDependencies. 11ty itself and any 11ty plugins must be in dependencies, not devDependencies, unless your build environment explicitly installs all packages.
Building the Landing Page: Templates, Layouts, and Data
With the project scaffolded, the next step is constructing the template hierarchy and wiring up the data layer. A landing page is structurally a composition of sections — hero, features, social proof, pricing, call to action — and the 11ty template system is well-suited to this compositional model.
The base layout establishes the HTML shell and injects the page-level variables. Nunjucks blocks allow child layouts to override specific regions — the <title>, meta tags, and body content — without duplicating the shell markup.
{# src/_includes/layouts/base.njk #}
<!DOCTYPE html>
<html lang="{{ locale | default('en') }}">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}{{ title }} | {{ site.name }}{% endblock %}</title>
<meta name="description" content="{{ description | default(site.description) }}" />
{# Open Graph tags for social sharing #}
<meta property="og:title" content="{{ title }}" />
<meta property="og:description" content="{{ description | default(site.description) }}" />
<meta property="og:image" content="{{ ogImage | default(site.defaultOgImage) | url }}" />
<meta property="og:type" content="website" />
<link rel="canonical" href="{{ page.url | absoluteUrl(site.url) }}" />
<link rel="stylesheet" href="/assets/css/main.css" />
{% block head %}{% endblock %}
</head>
<body class="{{ bodyClass | default('') }}">
{% include "partials/header.njk" %}
<main id="main-content">
{% block content %}{% endblock %}
</main>
{% include "partials/footer.njk" %}
<script src="/assets/js/main.js" defer></script>
{% block scripts %}{% endblock %}
</body>
</html>
The landing page template itself (src/index.njk) extends this base layout and composes the sections. Note the use of the data cascade: section content is pulled from data files rather than inlined in the template, which separates content from structure cleanly.
---
layout: layouts/base.njk
title: "Build Faster. Ship Smarter."
description: "The developer platform that eliminates infrastructure toil so your team can focus on what matters."
bodyClass: "page-home"
---
{% block content %}
{# Hero Section #}
{% include "partials/sections/hero.njk" %}
{# Features Grid #}
{% include "partials/sections/features.njk" %}
{# Social Proof / Testimonials #}
{% include "partials/sections/testimonials.njk" %}
{# Pricing #}
{% include "partials/sections/pricing.njk" %}
{# Final CTA #}
{% include "partials/sections/cta.njk" %}
{% endblock %}
The data layer is where 11ty's power becomes apparent. Global data files in src/_data/ are automatically available to every template in the project. Here is a realistic data file for a features section:
// src/_data/features.js
// Using a JS data file allows async fetching or computed values
module.exports = [
{
id: "fast-builds",
icon: "lightning",
headline: "Builds under 10 seconds",
body: "Our distributed build cache ensures your team never waits for a slow CI run again. Incremental builds detect exactly what changed.",
cta: { label: "See benchmarks", href: "/benchmarks" },
},
{
id: "zero-config",
icon: "settings",
headline: "Zero configuration required",
body: "Sensible defaults for TypeScript, ESLint, and testing mean you spend the first day shipping features, not configuring tools.",
cta: { label: "Read the docs", href: "/docs" },
},
{
id: "observability",
icon: "chart",
headline: "Observability built in",
body: "Distributed tracing, structured logging, and alerting come pre-wired. Integrate with your existing Datadog or Grafana setup in minutes.",
cta: { label: "Explore integrations", href: "/integrations" },
},
];
The corresponding partial iterates over this data and renders the feature cards:
{# src/_includes/partials/sections/features.njk #}
<section class="features" aria-labelledby="features-heading">
<div class="container">
<h2 id="features-heading" class="features__title">
Everything your team needs to move faster
</h2>
<p class="features__subtitle">
Stop stitching together tools. Start shipping.
</p>
<ul class="features__grid" role="list">
{% for feature in features %}
<li class="feature-card">
<div class="feature-card__icon" aria-hidden="true">
{% include "partials/icons/" + feature.icon + ".svg" %}
</div>
<h3 class="feature-card__headline">{{ feature.headline }}</h3>
<p class="feature-card__body">{{ feature.body }}</p>
{% if feature.cta %}
<a href="{{ feature.cta.href }}" class="feature-card__link">
{{ feature.cta.label }}
<span class="sr-only">({{ feature.headline }})</span>
</a>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
</section>
For site-wide configuration — the site name, URL, social links, default meta tags — a site.js global data file provides a single source of truth that all templates can reference as {{ site.name }}, {{ site.url }}, and so on. This pattern prevents the scattering of configuration values across dozens of templates.
// src/_data/site.js
module.exports = {
name: "Buildkit",
url: process.env.SITE_URL || "https://buildkit.dev",
description: "The developer platform that eliminates infrastructure toil.",
defaultOgImage: "/assets/images/og-default.png",
social: {
twitter: "https://twitter.com/buildkitdev",
github: "https://github.com/buildkit",
linkedin: "https://linkedin.com/company/buildkit",
},
nav: [
{ label: "Features", href: "/features" },
{ label: "Pricing", href: "/pricing" },
{ label: "Docs", href: "/docs" },
{ label: "Blog", href: "/blog" },
],
};
Asset Pipeline and Performance Optimization
11ty's passthrough copy handles static assets well for simple projects, but production landing pages typically require a more sophisticated asset pipeline: CSS minification, JavaScript bundling, image optimization, and cache-busting hashes. 11ty is deliberately agnostic about how you accomplish this — it does not bundle a CSS or JavaScript toolchain — which means you integrate the tools that best suit your team's existing workflow.
A common and well-supported pattern is to pair 11ty with esbuild for JavaScript and PostCSS for CSS. These tools can be invoked from npm scripts or integrated directly into the 11ty build via the eleventy.before and eleventy.after events, which fire synchronously before and after the 11ty build, respectively. This lets you process assets as part of the same build command without a separate orchestration layer.
// .eleventy.js — integrating esbuild and PostCSS as build hooks
const esbuild = require("esbuild");
const postcss = require("postcss");
const autoprefixer = require("autoprefixer");
const cssnano = require("cssnano");
const fs = require("fs/promises");
const path = require("path");
module.exports = function (eleventyConfig) {
const isProduction = process.env.NODE_ENV === "production";
// Build JavaScript bundle before 11ty starts
eleventyConfig.on("eleventy.before", async () => {
await esbuild.build({
entryPoints: ["src/assets/js/main.js"],
bundle: true,
minify: isProduction,
sourcemap: !isProduction,
outfile: "dist/assets/js/main.js",
target: ["es2020", "chrome80", "firefox80", "safari14"],
});
});
// Process CSS after 11ty finishes
eleventyConfig.on("eleventy.after", async () => {
const cssSource = await fs.readFile("src/assets/css/main.css", "utf8");
const plugins = [autoprefixer()];
if (isProduction) plugins.push(cssnano({ preset: "default" }));
const result = await postcss(plugins).process(cssSource, {
from: "src/assets/css/main.css",
to: "dist/assets/css/main.css",
});
await fs.mkdir("dist/assets/css", { recursive: true });
await fs.writeFile("dist/assets/css/main.css", result.css);
if (result.map) {
await fs.writeFile("dist/assets/css/main.css.map", result.map.toString());
}
});
eleventyConfig.addPassthroughCopy("src/assets/fonts");
eleventyConfig.addPassthroughCopy("src/assets/images");
return {
dir: {
input: "src",
output: "dist",
includes: "_includes",
data: "_data",
},
};
};
Image optimization deserves special attention. The @11ty/eleventy-img plugin is the most ergonomic way to handle image processing in an 11ty project. It generates multiple formats (WebP, AVIF, original), multiple sizes, and produces the <picture> element markup with appropriate srcset and sizes attributes. Crucially, it caches processed images to disk and only regenerates them when the source image changes, keeping build times fast even with a large image library.
// src/_includes/partials/image.njk — custom shortcode for optimized images
// Register in .eleventy.js:
// eleventyConfig.addAsyncShortcode("image", imageShortcode);
const Image = require("@11ty/eleventy-img");
const path = require("path");
async function imageShortcode(src, alt, widths = [400, 800, 1200], sizes = "100vw") {
const imagePath = path.join("src", src);
const metadata = await Image(imagePath, {
widths,
formats: ["avif", "webp", "jpeg"],
outputDir: "dist/assets/images/",
urlPath: "/assets/images/",
cacheOptions: {
duration: "1d", // Cache remote images for 1 day
},
});
const imageAttributes = {
alt,
sizes,
loading: "lazy",
decoding: "async",
};
return Image.generateHTML(metadata, imageAttributes);
}
Usage in a Nunjucks template becomes: {% image "/assets/images/hero.jpg", "Engineering team working at laptops", [800, 1600], "(min-width: 768px) 50vw, 100vw" %}. This single shortcode call generates an optimized, multi-format <picture> element with no manual work per-image — a significant productivity gain on image-heavy landing pages.
For fonts, the recommended approach is to self-host variable fonts with appropriate font-display: swap declarations and preload hints for the critical subset. Relying on Google Fonts introduces a third-party DNS lookup, a TCP connection, and a render-blocking resource — all of which are measurable negatives on Core Web Vitals scores. Fetching fonts at build time using a tool like fontsource and bundling them as static assets eliminates this dependency.
Trade-offs and Pitfalls
The simplicity of 11ty's output model comes with genuine trade-offs that teams should understand before committing to it. The most fundamental is the rebuild cycle. Every content change requires a full or incremental build before the output updates. For content editors who are accustomed to live preview in a CMS or WordPress, this friction is real. 11ty's development server with --watch mitigates this considerably for developers, but content editors typically need a more guided workflow — either a branch-based preview environment (Netlify Preview Deploys work well here) or a headless CMS with a live preview API.
A second trade-off is the absence of dynamic functionality by default. Form submissions, newsletter signups, dynamic search, and personalized content require explicit integration with third-party services or serverless functions. This is not unique to 11ty — it is inherent to any static site generator — but teams sometimes underestimate the scope of these integrations. A contact form requires a form-handling service (Formspree, Netlify Forms, Mailchimp) or a serverless function endpoint. Site search requires a client-side search library like Pagefind or an external search service. Each of these integrations adds operational surface area.
Build time is a consideration at scale. 11ty's incremental builds are fast for most projects, but sites with thousands of pages or complex data transformations can experience multi-minute build times. The 11ty team has made significant improvements to incremental builds in the 2.x and 3.x releases, and the --incremental flag limits rebuilds to changed files. However, teams building content-heavy sites should profile their build early and establish a baseline before the site grows to a scale where optimization becomes urgent.
Templating language fragmentation is a subtle pitfall. Because 11ty supports so many templating languages, projects without a clear convention can accumulate templates in Liquid, Nunjucks, and Markdown, all in the same _includes directory. This is technically valid but creates cognitive overhead for new team members. Establishing a convention — Nunjucks for structural templates, Markdown for content pages, JavaScript for data files — and enforcing it via code review prevents this drift.
Finally, the ecosystem around 11ty is smaller than React-based alternatives. Component libraries, starter templates, and plugins are abundant in the Next.js ecosystem and comparatively sparse in the 11ty ecosystem. Teams should expect to build more primitives from scratch and budget time accordingly. This is a genuine constraint, not a criticism — 11ty's model favors composition over convention — but it requires a more experienced frontend team that is comfortable working without a pre-built component library.
Best Practices for Production Deployments
Before deploying a landing page built with 11ty, there are several practices that separate a robust production site from one that degrades under real-world conditions. The first is a comprehensive robots.txt and sitemap. 11ty does not generate these automatically, but the @11ty/eleventy-plugin-sitemap plugin and a simple template-generated robots.txt cover both. A sitemap is essential for landing pages — it signals to search engines which URLs are canonical, which is particularly important if your site is deployed on a custom domain with a non-root path prefix.
Environment-specific configuration is often handled poorly in static site projects. The approach is to use process.env variables in JavaScript data files and pass them through the CI/CD pipeline, keeping sensitive values (API keys for CMS access at build time, analytics IDs) out of the template layer and out of version control. A .env.example file documents the required variables without exposing their values.
Content Security Policy (CSP) headers are frequently overlooked on static sites because developers associate them with server-rendered applications. But a landing page that loads third-party analytics, chat widgets, or A/B testing scripts is a real XSS surface. CDN platforms like Netlify and Cloudflare Pages support custom response headers via netlify.toml or _headers files, which allows you to define a strict CSP without a server.
# netlify.toml
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
Content-Security-Policy = """
default-src 'self';
script-src 'self' 'unsafe-inline' https://analytics.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
"""
Permissions-Policy = "camera=(), microphone=(), geolocation=()"
[[redirects]]
from = "/old-landing"
to = "/new-landing"
status = 301
Cache control headers deserve explicit configuration. Static assets with hashed filenames (e.g., main.a3f9c2.js) should receive long-lived cache headers (Cache-Control: public, max-age=31536000, immutable) since their content is guaranteed not to change. HTML files should receive short-lived or no-cache headers (Cache-Control: public, max-age=0, must-revalidate) so that content updates propagate immediately. Without this distinction, a CDN will serve stale HTML while serving fresh assets, leading to version mismatches.
Accessibility is a best practice that is both a compliance requirement and a performance factor. 11ty's output is plain HTML, which means accessibility is entirely your responsibility — there is no framework-level accessibility layer as there is with some React component libraries. Practical steps include: using semantic HTML elements (<nav>, <main>, <section>, <article>), providing aria-labelledby on landmark regions, ensuring all interactive elements are keyboard reachable, maintaining a 4.5:1 color contrast ratio for body text, and testing with a screen reader (NVDA on Windows, VoiceOver on macOS) before launch.
Monitoring a static site in production means monitoring the CDN, not a server. Set up uptime monitoring on the canonical URL and a sample of critical pages using a service like Better Uptime, Checkly, or similar. Track Core Web Vitals via Google Search Console and Lighthouse CI in your build pipeline so that performance regressions are caught before they reach production. A failing Lighthouse CI check should block a deployment the same way a failing unit test does.
Key Takeaways
The following five steps distill the practical guidance in this article into actions you can apply immediately:
-
Adopt the data cascade pattern early. Separate content (data files) from structure (templates) from presentation (CSS) from the start. Retroactively refactoring a landing page where content is hardcoded into templates is painful and error-prone.
-
Use
eleventy.beforeandeleventy.afterhooks for your asset pipeline. Integrating esbuild and PostCSS directly into the 11ty lifecycle gives you a singlenpm run buildcommand for the entire site without a Makefile or Gulp task runner. -
Install and configure
@11ty/eleventy-imgon day one. Image optimization is the single highest-leverage performance improvement available on a landing page. Retroactively adding it to an existing project requires updating every image reference. -
Define a CSS architecture before writing your first selector. 11ty has no opinion on CSS methodology. Choose and document a system — BEM, CUBE CSS, utility-first — before the project starts and enforce it in code review.
-
Set up Lighthouse CI as a build gate. Catching a Largest Contentful Paint regression in CI is orders of magnitude cheaper than discovering it after launch via a Core Web Vitals report.
Analogies and Mental Models
Thinking about 11ty's data cascade is easiest with a spreadsheet analogy. Imagine each template as a row in a spreadsheet. Global data files fill in default column values for every row. Directory data files override those defaults for all rows in a given section. Template front matter overrides at the individual row level. Computed data fills in derived columns based on other column values. The "most specific scope wins" rule mirrors how CSS specificity works — and once you internalize it, debugging unexpected variable values becomes intuitive.
The layout chain is best understood as function composition. Each layout wraps the content of its child, just as compose(f, g)(x) applies g first and then passes the result to f. A base layout that wraps a page layout that wraps a section template is equivalent to renderBase(renderPage(renderSection(data))). This mental model clarifies why you cannot access a child layout's variables from a parent layout — data flows downward through the chain, not upward.
80/20 Insight
If you master three concepts in 11ty, you can build and maintain any landing page effectively. First, the data cascade — understanding where data comes from and how it is merged. Second, the layout chain — understanding how templates extend and compose. Third, the addPassthroughCopy, addPlugin, and addShortcode configuration APIs — understanding how to extend 11ty's behavior without fighting its defaults.
The remaining complexity — custom collections, pagination, directory output, serverless integration — represents features you will use on specific projects for specific reasons. But the three foundational concepts above apply on every project, every time. Invest the most time there.
Conclusion
Eleventy occupies a unique and valuable position in the modern frontend toolchain. It is not a framework in the React sense — it prescribes no component model, no state management, no client-side runtime. It is a build-time rendering engine that takes your content, templates, and data, and produces clean HTML. For a landing page, where performance, maintainability, and simplicity are the dominant concerns, that model is almost always the right choice.
The practical patterns in this guide — the project structure, the data file conventions, the asset pipeline integration, the deployment configuration — are not academic. They reflect the decisions that teams working on production landing pages make repeatedly, often by learning the hard way. Starting with these patterns means you avoid the common pitfalls and spend your time on the work that actually matters: writing clear copy, designing a compelling visual hierarchy, and shipping.
11ty rewards engineers who understand the web platform. Because it does not abstract away HTML, CSS, and the browser, it requires you to know these things well. In exchange, you get a tool that will not impose artificial complexity on a problem that does not require it. For a landing page, that trade is almost always worth making.
References
- Eleventy (11ty) Official Documentation. 11ty.dev. https://www.11ty.dev/docs/
- Eleventy Image Plugin. @11ty/eleventy-img. https://www.11ty.dev/docs/plugins/image/
- Eleventy HTML Base Plugin. https://www.11ty.dev/docs/plugins/html-base/
- Leatherman, Zach. "Eleventy's Data Cascade." 11ty.dev. https://www.11ty.dev/docs/data-cascade/
- Netlify. "File-Based Configuration." Netlify Docs. https://docs.netlify.com/configure-builds/file-based-configuration/
- Google. "Core Web Vitals." web.dev. https://web.dev/vitals/
- MDN Web Docs. "Content Security Policy (CSP)." developer.mozilla.org. https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- esbuild Documentation. esbuild.github.io. https://esbuild.github.io/
- PostCSS Documentation. postcss.org. https://postcss.org/
- Pagefind — Static Low-Bandwidth Search. https://pagefind.app/
- Lighthouse CI GitHub Action. github.com/GoogleChrome/lighthouse-ci. https://github.com/GoogleChrome/lighthouse-ci
- WCAG 2.1 — Web Content Accessibility Guidelines. W3C. https://www.w3.org/TR/WCAG21/
- Jamstack.org. "What is Jamstack?" https://jamstack.org/what-is-jamstack/