פיתוח תבניות Umbraco מותאמות אישית
לקוח מגיע עם משימה: "אני צריך אתר Umbraco אבל תבניות מוכנות לא מתאימות — אני צריך פיתוח תבנית Umbraco מותאמת אישית למבנה לא סטנדרטי." אנחנו לא לוקחים תבנית מהמרקטפלייס: אנחנו כותבים Razor views, controllers ו-View Components מאפס עבור Document Type ספציפי. אין markup מיותר, אין פריצות — רק מה שהפרויקט צריך. תבנית מותאמת אישית טוענת עמודים 40% מהר יותר מפתרון מוכן בשל בקשות ממוזערות ופיצול bundles.
איך להתאים תבנית מבלי לאבד ביצועים?
השלב הראשון הוא לסדר את ה-Layout. כל עמוד יורש את ה-layout המשותף, אבל אם דחוסים בו יותר מדי לוגיקה, ה-TTFB סובל. אנחנו מעבירים חלקים סטטיים (header, footer) ל-partials נפרדים, ודינמיים דרך @await Component.InvokeAsync(). זה מאפשר caching חלקי ומפחית עומס על מסד הנתונים. לדוגמה, בפרויקט אחד LCP ירד מ-2.8 שניות ל-1.1 שניות לאחר refactoring של ה-Layout.
לפי המלצות Google Core Web Vitals, LCP צריך להיות מתחת ל-2.5 שניות. Layout בסיסי:
@* Views/Shared/_Layout.cshtml *@
@using Umbraco.Cms.Web.Common.PublishedModels
@inject IPublishedValueFallback PublishedValueFallback
<!DOCTYPE html>
<html lang="@System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@ViewData["Title"] – @ViewBag.SiteName</title>
<meta name="description" content="@ViewData["Description"]">
<link rel="stylesheet" href="~/css/app.css" asp-append-version="true">
</head>
<body class="@ViewData["BodyClass"]">
@await Html.PartialAsync("_Navigation")
<main>@RenderBody()</main>
@await Html.PartialAsync("_Footer")
<script src="~/js/app.js" asp-append-version="true" defer></script>
@RenderSection("scripts", required: false)
</body>
</html>תרחיש נפוץ: הלקוח רוצה כותרת h1 מותאמת אישית ותיאור SEO בכל עמוד. אנחנו פותרים זאת עם מודל strongly-typed הממופה מ-IPublishedContent. אנחנו משתמשים ב-Umbraco Mapper כדי להפוך המרות לאוטומטיות (פרטים נוספים ב-תיעוד Umbraco).
דוגמה ל-controller של מאמר ב-C#:
public class ArticleController : RenderController
{
private readonly IUmbracoMapper _mapper;
private readonly IRelatedContentService _relatedService;
public ArticleController(ILogger<ArticleController> logger, ICompositeViewEngine viewEngine, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoMapper mapper, IRelatedContentService relatedService) : base(logger, viewEngine, umbracoContextAccessor)
{
_mapper = mapper;
_relatedService = relatedService;
}
public override IActionResult Index()
{
if (CurrentPage is not Article article)
return NotFound();
var model = new ArticleViewModel
{
Title = article.Title,
BodyHtml = article.Body?.ToString(),
PublishDate = article.PublishDate,
SeoTitle = article.SeoTitle,
SeoDescription = article.SeoDescription,
CoverImage = article.CoverImage?.First() as IPublishedContent,
Category = article.ArticleCategory?.First() as IPublishedContent,
RelatedArticles = _relatedService.GetRelated(article, 3),
};
return CurrentTemplate(model);
}
} למה לבחור בתבניות Umbraco מותאמות אישית על פני פתרונות מוכנים?
| קריטריון | תבנית מוכנה | תבנית מותאמת אישית |
|---|---|---|
| ביצועים | לעתים קרובות עמוסה בסקריפטים מיותרים | בקשות מינימליות, Partial Views מותאמים |
| מבנה SEO | Markup קבוע, קשה לשינוי | שליטה מלאה ב-HTML, כותרות, microdata |
| מהירות טעינה | LCP עשוי להיות > 2.5 שניות עקב bundles | פיצול bundles, תמונות lazy דרך ImageSharp, WebP |
| יכולת הרחבה | כל שינוי דורש פריצות ותיקונים | ארכיטקטורה נקייה, דפוס Repository, קל להוסיף בלוקים |
הטבלה מראה שתבנית מותאמת אישית מנצחת בכל המדדים המרכזיים. בנוסף, חיסכון ברישיונות ותמיכה יכול להגיע עד 30% בהשוואה לפתרון מוכן. העלות של תבנית מותאמת אישית נמוכה יותר מקנייה ושינוי מתמיד של תבנית מוכנה. לוחות הזמנים לפיתוח שקופים וקבועים במפרט הטכני.
תהליך התאמת תבנית Umbraco
- ניתוח: לימוד Document Types, מבנה תוכן, דרישות ביצועים.
- עיצוב Layout: יצירת layout, הגדרת אזורים ל-Partial Views.
- פיתוח Controller: כתיבת ViewModels strongly-typed עם הזרקת תלות לכל Document Type.
- יישום בלוקים: הגדרת Block List / Block Grid עם partials מותאמים, שילוב ImageSharp לתמונות אדפטיביות.
- בדיקות: בדיקת Core Web Vitals, ביטול שאילתות N+1, הגדרת caching.
- פריסה: מסירת קוד, תיעוד וגישה.
שלבי פיתוח ולוחות זמנים
| שלב | משך |
|---|---|
| ניתוח ואודיט | 1–2 ימים |
| עיצוב Layout | 1–2 ימים |
| פיתוח Controller | 2–4 ימים |
| יישום בלוקים | 3–5 ימים |
| בדיקות ואופטימיזציה | 1–2 ימים |
| פריסה ותיעוד | יום אחד |
טעויות נפוצות בהתאמת תבניות Umbraco
שאילתת N+1 — כל בלוק Hero טוען תמונה באופן עצמאי. פתרון: הזרקת שירות תמונות ל-controller והעברת URLs מוכנים.
Hydration mismatch — בעת caching של partial views, הנתונים הופכים למיושנים. השתמשו ב-@* Views/Shared/_Layout.cshtml *@ @using Umbraco.Cms.Web.Common.PublishedModels @inject IPublishedValueFallback PublishedValueFallback <!DOCTYPE html> <html lang="@System.Threading.Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>@ViewData["Title"] – @ViewBag.SiteName</title> <meta name="description" content="@ViewData["Description"]"> <link rel="stylesheet" href="~/css/app.css" asp-append-version="true"> </head> <body class="@ViewData["BodyClass"]"> @await Html.PartialAsync("_Navigation") <main>@RenderBody()</main> @await Html.PartialAsync("_Footer") <script src="~/js/app.js" asp-append-version="true" defer></script> @RenderSection("scripts", required: false) </body> </html> על רכיבים דינמיים.
כפילות ניווט — תפריט רב-רמות עם תבניות נפרדות לכל רמה. במקום זאת, השתמשו ב-partial רקורסיבי יחיד public class ArticleController : RenderController { private readonly IUmbracoMapper _mapper; private readonly IRelatedContentService _relatedService; public ArticleController(ILogger<ArticleController> logger, ICompositeViewEngine viewEngine, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoMapper mapper, IRelatedContentService relatedService) : base(logger, viewEngine, umbracoContextAccessor) { _mapper = mapper; _relatedService = relatedService; } public override IActionResult Index() { if (CurrentPage is not Article article) return NotFound(); var model = new ArticleViewModel { Title = article.Title, BodyHtml = article.Body?.ToString(), PublishDate = article.PublishDate, SeoTitle = article.SeoTitle, SeoDescription = article.SeoDescription, CoverImage = article.CoverImage?.First() as IPublishedContent, Category = article.ArticleCategory?.First() as IPublishedContent, RelatedArticles = _relatedService.GetRelated(article, 3), }; return CurrentTemplate(model); } } .
דוגמה לניווט רקורסיבי
@* Views/Partials/_Navigation.cshtml *@
@using Umbraco.Cms.Web.Common.UmbracoContext
@inject IUmbracoContextAccessor UmbracoContextAccessor
@{
var ctx = UmbracoContextAccessor.GetRequiredUmbracoContext();
var root = ctx.Content?.GetAtRoot().FirstOrDefault();
var nav = root?.Children.Where(n => n.Value<bool>("showInNav"));
}
<nav class="main-nav">
@foreach (var item in nav ?? [])
{
<a href="@item.Url()" class="@(item.IsAncestorOrSelf(ctx.PublishedRequest?.PublishedContent) ? "active" : "")">
@item.Name
</a>
}
</nav> מה כלול
- פיתוח Layout ו-Partial Views (ניווט, footer, pop-ups)
- מודלים ViewModel strongly-typed לכל Document Type
- Controllers ב-C# עם הזרקת תלות
- עורך בלוקים (Block List / Block Grid) עם partials מותאמים
- View Components למאקרו (פירורי לחם, תפריטים, ווידג'טים)
- תמונות אדפטיביות עם WebP ו-srcset
- Markup מינימלי של Umbraco באמצעות Tailwind או CSS Grid — בתיאום
- תיעוד וגישה (פאנל ניהול, FTP, מאגר)
לוחות זמנים ועלות
לוחות הזמנים תלויים במספר סוגי העמודים ובמורכבות הבלוקים. אנחנו עובדים בטווח:
- סט בסיסי (Layout + 5–6 סוגי עמודים + בלוקים): 2–3 שבועות.
- תבנית מורכבת אחת עם controller מותאם ו-caching: 3–5 ימים.
העלות מחושבת באופן אישי לאחר אודיט של המבנה הנוכחי. אנחנו לא נוקבים במחיר בעיוורון — קודם אנחנו מכירים את הפרויקט. נעריך את הפרויקט שלכם ביום עסקים אחד ונספק הערכה מדויקת ולוח זמנים. הניסיון שלנו של 7+ שנים ו-50+ פרויקטי Umbraco מבטיח תוצאות צפויות. צרו קשר לדיון בפרטים — קבלו ייעוץ. הזמינו פיתוח תבנית Umbraco מותאמת אישית — קבלו ביצועים מותאמים לפרויקט שלכם.







