פיתוח אתר Eleventy: הגדרה, תצורה ופריסה

אתרים סטטיים על Eleventy (11ty) מציעים טעינה מהירה ו-HTML נקי ללא JavaScript נוסף, אך הגדרת כלי הבנייה ומפל הנתונים לעיתים קרובות מאתגרת. אנחנו מפתחים אתרי Eleventy במפתח מלא: אנו מגדירים תצורה, תבניות ופריסה כך שתקבלו משאב אמין ומהיר. הצוות שלנו מטפל בכל המחזור—מבדיקה ועד תמיכה—ומבטיח פעילות יציבה וסקלביליות של הפרויקט שלכם.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
פיתוח אתר Eleventy: הגדרה, תצורה ופריסה
בינוני
~3-5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1502
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1344
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1052
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1307
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1049
  • פיתוח אתר לחברת FIXPER
    פיתוח אתר לחברת FIXPER
    1033

מדריך זה מכסה פיתוח אתרים עם Eleventy, תוך התמקדות בהתקנת מחולל האתרים הסטטיים 11ty, כולל eleventy.config.js, תבניות Nunjucks, מפל הנתונים של Eleventy, פגינציה של 11ty, אופטימיזציית LCP של Eleventy, מהירות בנייה של 11ty, יצירת אתרים סטטיים עם Node.js, מעבר מ-Jekyll ל-11ty, חבילת Vite + Eleventy, ופריסת אתרים סטטיים. הערה: כשקיבלנו את פרויקט Eleventy (11ty) הראשון שלנו, הלקוח רצה דף נחיתה מהיר ללא הנפח האופייני של WordPress. מחולל האתרים הסטטיים הבטיח טעינות מיידיות — וקיים: האתר הסופי קיבל 100 נקודות Lighthouse על LCP (<1.2 שניות) ו-CLS (0.02) ללא שורת אופטימיזציית JS אחת. אבל ההתקנה דרשה צלילה עמוקה למפל הנתונים ולכלי הבנייה. הנה מה שלמדנו על עשייה נכונה. עבור אתר טיפוסי עם 500 עמודים, עלויות האחסון יורדות מ-$100 לחודש (שרת) ל-$5 לחודש (CDN), חיסכון של $1140 בשנה.

בעיות שאנחנו פותרים

הימנעות משאילתות N+1 במהלך הבנייה

כאשר מייצרים אתר עם 1000+ עמודים, תגיות ופגינציה, נקודת הכאב העיקרית היא ביצועי הבנייה. ב-Eleventy, אוספים מסוננים בזמן בנייה, לא בזמן ריצה. ללא אופטימיזציה, כל קריאה ל-getFilteredByGlob גורמת לבקשות קבצים מדורגות למערכת הקבצים. הפתרון הוא לשמור אוספים במטמון בנתונים גלובליים ולעשות בהם שימוש חוזר דרך addGlobalData. זה מקצר את זמן הבנייה מ-40 שניות ל-5 שניות בפרויקט בדיקה עם 2000 עמודים. בנוסף, passthroughCopy מוגדר היטב מפחית העתקה מיותרת, ושימוש ב-eleventyConfig.addCollection עם סינון מובנה מניב תוספת של 10–15% במהירות.

חוסר התאמה בהידרציה: לא הבעיה שלך

בניגוד ל-Next.js או Nuxt, Eleventy לא מבצע הידרציה של הלקוח כלל. Eleventy לא משתמש ב-JavaScript בצד הלקוח. התוצאה היא HTML ו-CSS טהורים. אין פער בין HTML של השרת ל-DOM של הדפדפן. מדדי Core Web Vitals מרוויחים: INP נשאר נמוך (20ms במובייל) כי אין מסגרת JavaScript בעמוד. עבור חנות מקוונת עם 5000 מוצרים, זה נותן LCP <1.2 שניות ללא מאמץ נוסף. היעדר ההידרציה גם מבטל מחלקה שלמה של שגיאות חוסר התאמה במצב — אתה מקבל את ה-HTML המוכן כפי שהוא.

איך אנחנו עושים את זה

ארכיטקטורת פרויקט

אנחנו עוקבים אחר מבנה מודולרי שבו _data/, _includes/ ואוספים מופרדים.

mysite/
├── .eleventy.js
├── src/
│   ├── _data/
│   │   ├── site.js
│   │   ├── navigation.json
│   │   └── team.yaml
│   ├── _includes/
│   │   ├── layouts/
│   │   │   ├── base.njk
│   │   │   └── post.njk
│   │   └── components/
│   │       ├── card.njk
│   │       └── hero.njk
│   ├── blog/
│   │   ├── blog.json
│   │   └── *.md
│   ├── services/
│   ├── assets/
│   │   ├── css/
│   │   └── js/
│   └── index.njk
├── package.json
└── _site/

הגדרת eleventy.config.js

const { EleventyHtmlBasePlugin } = require("@11ty/eleventy");
const pluginRss = require("@11ty/eleventy-plugin-rss");
const pluginSyntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
const Image = require("@11ty/eleventy-img");
const yaml = require("js-yaml");
const path = require("path");

module.exports = function(eleventyConfig) {
  // Плагины
  eleventyConfig.addPlugin(EleventyHtmlBasePlugin);
  eleventyConfig.addPlugin(pluginRss);
  eleventyConfig.addPlugin(pluginSyntaxHighlight, {
    preAttributes: { tabindex: 0 }
  });

  // Парсер YAML для _data
  eleventyConfig.addDataExtension("yaml,yml", contents => yaml.load(contents));

  // Passthrough copy
  eleventyConfig.addPassthroughCopy("src/assets/fonts");
  eleventyConfig.addPassthroughCopy({ "src/assets/images/favicon": "/" });

  // Фильтры
  eleventyConfig.addFilter("dateFormat", function(date, format = "dd.MM.yyyy") {
    return new Intl.DateTimeFormat("ru-RU").format(new Date(date));
  });

  eleventyConfig.addFilter("readingTime", function(content) {
    const words = content.split(/\s+/).length;
    const minutes = Math.ceil(words / 200);
    return `${minutes} мин`;
  });

  eleventyConfig.addFilter("excerpt", function(content, length = 160) {
    const stripped = content.replace(/<[^>]*>/g, '');
    return stripped.length > length ? stripped.substring(0, length).trim() + '…' : stripped;
  });

  // Async Image Shortcode
  eleventyConfig.addAsyncShortcode("image", async function(src, alt, sizes = "100vw") {
    const metadata = await Image(src, {
      widths: [320, 640, 960, 1280],
      formats: ["avif", "webp", "jpeg"],
      outputDir: "./_site/assets/images/",
      urlPath: "/assets/images/",
    });

    const imageAttributes = {
      alt,
      sizes,
      loading: "lazy",
      decoding: "async",
    };

    return Image.generateHTML(metadata, imageAttributes);
  });

  // Коллекции
  eleventyConfig.addCollection("blog", function(collectionApi) {
    return collectionApi.getFilteredByGlob("src/blog/*.md")
      .filter(post => !post.data.draft)
      .reverse();
  });

  eleventyConfig.addCollection("tagList", function(collectionApi) {
    const tagSet = new Set();
    collectionApi.getAll().forEach(item => {
      (item.data.tags || []).forEach(tag => {
        if (!["post", "all"].includes(tag)) tagSet.add(tag);
      });
    });
    return [...tagSet].sort();
  });

  // Настройки Markdown
  const markdownIt = require("markdown-it");
  const markdownItAnchor = require("markdown-it-anchor");
  const markdownItAttrs = require("markdown-it-attrs");

  const md = markdownIt({
    html: true,
    linkify: true,
    typographer: true
  })
    .use(markdownItAnchor, {
      permalink: markdownItAnchor.permalink.ariaHidden({ placement: "after" }),
      slugify: s => s.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '')
    })
    .use(markdownItAttrs);

  eleventyConfig.setLibrary("md", md);

  // Конфиг директорий
  return {
    dir: {
      input: "src",
      output: "_site",
      includes: "_includes",
      data: "_data",
    },
    htmlTemplateEngine: "njk",
    markdownTemplateEngine: "njk",
    templateFormats: ["md", "njk", "html"],
  };
};

תבניות Nunjucks

התבנית הבסיסית mysite/ ├── .eleventy.js ├── src/ │ ├── _data/ │ │ ├── site.js │ │ ├── navigation.json │ │ └── team.yaml │ ├── _includes/ │ │ ├── layouts/ │ │ │ ├── base.njk │ │ │ └── post.njk │ │ └── components/ │ │ ├── card.njk │ │ └── hero.njk │ ├── blog/ │ │ ├── blog.json │ │ └── *.md │ ├── services/ │ ├── assets/ │ │ ├── css/ │ │ └── js/ │ └── index.njk ├── package.json └── _site/ מגדירה את מעטפת ה-HTML עם תגיות SEO, קישורי CSS ו-JS.

{# src/_includes/layouts/base.njk #}
<!DOCTYPE html>
<html lang="{{ site.lang | default('ru') }}">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{% if title %}{{ title }} | {{ site.title }}{% else %}{{ site.title }}{% endif %}</title>
  <meta name="description" content="{{ description | default(site.description) }}">
  <meta property="og:title" content="{{ title | default(site.title) }}">
  <meta property="og:url" content="{{ site.url }}{{ page.url }}">
  <link rel="canonical" href="{{ site.url }}{{ page.url }}">
  <link rel="stylesheet" href="/assets/css/main.css">
</head>
<body>
  {% include "components/header.njk" %}
  <main>
    {% block content %}{{ content | safe }}{% endblock %}
  </main>
  {% include "components/footer.njk" %}
  <script src="/assets/js/main.js" defer></script>
</body>
</html>

מפל נתונים

Eleventy תומך במפל נתונים — עדיפות מגלובלי למקומי. נתונים גלובליים מוגדרים ב-const { EleventyHtmlBasePlugin } = require("@11ty/eleventy"); const pluginRss = require("@11ty/eleventy-plugin-rss"); const pluginSyntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight"); const Image = require("@11ty/eleventy-img"); const yaml = require("js-yaml"); const path = require("path"); module.exports = function(eleventyConfig) { // Плагины eleventyConfig.addPlugin(EleventyHtmlBasePlugin); eleventyConfig.addPlugin(pluginRss); eleventyConfig.addPlugin(pluginSyntaxHighlight, { preAttributes: { tabindex: 0 } }); // Парсер YAML для _data eleventyConfig.addDataExtension("yaml,yml", contents => yaml.load(contents)); // Passthrough copy eleventyConfig.addPassthroughCopy("src/assets/fonts"); eleventyConfig.addPassthroughCopy({ "src/assets/images/favicon": "/" }); // Фильтры eleventyConfig.addFilter("dateFormat", function(date, format = "dd.MM.yyyy") { return new Intl.DateTimeFormat("ru-RU").format(new Date(date)); }); eleventyConfig.addFilter("readingTime", function(content) { const words = content.split(/\s+/).length; const minutes = Math.ceil(words / 200); return `${minutes} мин`; }); eleventyConfig.addFilter("excerpt", function(content, length = 160) { const stripped = content.replace(/<[^>]*>/g, ''); return stripped.length > length ? stripped.substring(0, length).trim() + '…' : stripped; }); // Async Image Shortcode eleventyConfig.addAsyncShortcode("image", async function(src, alt, sizes = "100vw") { const metadata = await Image(src, { widths: [320, 640, 960, 1280], formats: ["avif", "webp", "jpeg"], outputDir: "./_site/assets/images/", urlPath: "/assets/images/", }); const imageAttributes = { alt, sizes, loading: "lazy", decoding: "async", }; return Image.generateHTML(metadata, imageAttributes); }); // Коллекции eleventyConfig.addCollection("blog", function(collectionApi) { return collectionApi.getFilteredByGlob("src/blog/*.md") .filter(post => !post.data.draft) .reverse(); }); eleventyConfig.addCollection("tagList", function(collectionApi) { const tagSet = new Set(); collectionApi.getAll().forEach(item => { (item.data.tags || []).forEach(tag => { if (!["post", "all"].includes(tag)) tagSet.add(tag); }); }); return [...tagSet].sort(); }); // Настройки Markdown const markdownIt = require("markdown-it"); const markdownItAnchor = require("markdown-it-anchor"); const markdownItAttrs = require("markdown-it-attrs"); const md = markdownIt({ html: true, linkify: true, typographer: true }) .use(markdownItAnchor, { permalink: markdownItAnchor.permalink.ariaHidden({ placement: "after" }), slugify: s => s.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '') }) .use(markdownItAttrs); eleventyConfig.setLibrary("md", md); // Конфиг директорий return { dir: { input: "src", output: "_site", includes: "_includes", data: "_data", }, htmlTemplateEngine: "njk", markdownTemplateEngine: "njk", templateFormats: ["md", "njk", "html"], }; }; (אובייקט עם title, url וכו'), נתוני תיקייה ב-_includes/layouts/base.njk, ו-front matter של פוסט בודד עוקף הכל. היררכיה זו מאפשרת ניהול תוכן גמיש ללא כפילויות.

פגינציה

{# src/blog/index.njk #}
---
title: Блог
pagination:
  data: collections.blog
  size: 12
  alias: posts
  reverse: true
permalink: "/blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}"
---
<div class="posts-grid">
  {% for post in posts %}
    {% include "components/post-card.njk" %}
  {% endfor %}
</div>
{% if pagination.pages.length > 1 %}
  <nav class="pagination">
    {% if pagination.href.previous %}
      <a href="{{ pagination.href.previous }}">← Назад</a>
    {% endif %}
    <span>{{ pagination.pageNumber + 1 }} / {{ pagination.pages.length }}</span>
    {% if pagination.href.next %}
      <a href="{{ pagination.href.next }}">Вперёд →</a>
    {% endif %}
  </nav>
{% endif %}

אינטגרציה עם Vite

Vite בונה נכסים לתוך {# src/_includes/layouts/base.njk #} <!DOCTYPE html> <html lang="{{ site.lang | default('ru') }}"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% if title %}{{ title }} | {{ site.title }}{% else %}{{ site.title }}{% endif %}</title> <meta name="description" content="{{ description | default(site.description) }}"> <meta property="og:title" content="{{ title | default(site.title) }}"> <meta property="og:url" content="{{ site.url }}{{ page.url }}"> <link rel="canonical" href="{{ site.url }}{{ page.url }}"> <link rel="stylesheet" href="/assets/css/main.css"> </head> <body> {% include "components/header.njk" %} <main> {% block content %}{{ content | safe }}{% endblock %} </main> {% include "components/footer.njk" %} <script src="/assets/js/main.js" defer></script> </body> </html> , בעוד Eleventy מייצר HTML וקבצים סטטיים. הריץ אותם במקביל דרך _data/site.js: blog.json ו-{# src/blog/index.njk #} --- title: Блог pagination: data: collections.blog size: 12 alias: posts reverse: true permalink: "/blog/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber + 1 }}/{% endif %}" --- <div class="posts-grid"> {% for post in posts %} {% include "components/post-card.njk" %} {% endfor %} </div> {% if pagination.pages.length > 1 %} <nav class="pagination"> {% if pagination.href.previous %} <a href="{{ pagination.href.previous }}">← Назад</a> {% endif %} <span>{{ pagination.pageNumber + 1 }} / {{ pagination.pages.length }}</span> {% if pagination.href.next %} <a href="{{ pagination.href.next }}">Вперёд →</a> {% endif %} </nav> {% endif %} . קבצי Vite מקושרים בתבניות כרגיל.

השוואת Eleventy עם Hugo ו-Jekyll

קריטריון Eleventy Hugo Jekyll
מהירות בנייה בינונית (5 שניות ל-2000 עמודים) גבוהה נמוכה
גמישות תבניות גבוהה (Nunjucks) בינונית (Go) נמוכה (Liquid)
מערכת אקולוגית Node.js Go Ruby
קלות התחלה גבוהה בינונית בינונית
קהילה ותוספים מודולי Node.js רבים צומחת בוגרת אך סטטית

הניסיון שלנו מראה: אם הצוות שלך יודע JavaScript וצריך שליטה גמישה בסימון, Eleventy מנצח. Hugo מצטיין במהירות בנייה, אבל תבניות ה-Go שלו פחות גמישות. Jekyll קשור ל-Ruby ולעתים קרובות גורם לבעיות גרסאות. Eleventy מהיר פי 3 לפיתוח מ-Jekyll, וזמני הבנייה מהירים פי 2 מ-Hugo עבור 2000 עמודים. מעבר מ-Jekyll ל-Eleventy בדרך כלל מפחית את זמני הבנייה ב-60%. בנוסף, מעבר ל-Eleventy מקצץ את עלויות האחסון ב-40%: קבצים סטטיים ניתן לשרת דרך CDN כמעט בחינם, בעוד שאתרים דינמיים דורשים שרת.

תהליך עבודה

  1. ניתוח — קביעת סוגי תוכן, מבנה אוספים, דפוסי נתונים.
  2. עיצוב — יצירת שלד של _site/assets, concurrently, אוספים עם נתוני בדיקה.
  3. יישום — בניית תבניות Nunjucks, הגדרת תוספים (RSS, תמונות, הדגשת תחביר).
  4. בדיקה — אימות בנייה עם 500+ עמודים, מדידת LCP/CLS דרך Lighthouse.
  5. פריסה — הגדרת CI/CD (Vercel, Netlify, Cloudflare Pages), חיבור CDN.
דוגמת הגדרת פריסה ל-Netlify ציין ב-`netlify.toml` את פקודות הבנייה ואת תיקיית הפרסום:
[build]
command = "npm run build"
publish = "_site"

[[redirects]]
from = "/*"
to = "/404.html"
status = 404

Netlify מגיש אוטומטית קבצים סטטיים דרך CDN עם תמיכה בדחיסת Brotli.

מה כלול בפרויקט מפתח

רכיב תיאור
מאגר קוד GitLab / GitHub עם הגדרת הגנת ענפים
תיעוד README עם סכימת נתונים ופקודות
גישה לוח ניהול CMS (אם נדרש) + אחסון
הדרכה שיחת וידאו של שעה לעורכים
תמיכה שבועיים של תיקונים חינם לאחר ההשקה

לוחות זמנים משוערים

  • אתר על תבנית התחלה עם תוכן מותאם — 4–6 ימים.
  • פיתוח מאפס עם אוספים מותאמים, פגינציה, אופטימיזציית תמונות, CI/CD — 2–3 שבועות.
  • פורטל גדול עם עשרות סוגי תוכן, תמיכה רב-לשונית, אינטגרציות CMS — 1–2 חודשים.

התמחור מחושב באופן אישי — נעריך את הפרויקט שלך תוך יום אחד. אנחנו בשוק מעל 5 שנים והשלמנו 40+ פרויקטים על מחוללי אתרים סטטיים. הלקוחות שלנו מקבלים ערבות בנייה ותמיכה לאחר ההשקה. צור קשר לקבלת ייעוץ ולהזמנת פיתוח Eleventy. תמחור פרויקט טיפוסי: מ-$1,500 לאתר התחלתי, $5,000 לפורטל גדול.