sitecore-classic

SKILL.md

Bundled Skills

This plugin includes the following additional skills for comprehensive development context:

Skill Purpose
frontend-classic CSS/SASS organization, JavaScript/jQuery patterns, BEM naming
frontend-razor Razor view syntax, layouts, partials, tag helpers
backend-csharp C#/.NET DI patterns, service architecture, async/await
fullstack-classic jQuery AJAX integration, form handling, anti-forgery tokens

These skills are automatically included when you install the Sitecore Classic Analyzer plugin.

Sitecore 10.x Development Patterns

Helix Architecture

Layer Responsibilities

Foundation Layer

  • Cross-cutting concerns shared across features
  • Examples: Logging, Serialization, DI registration, Indexing, ORM
  • No business logic, only technical infrastructure
  • Can reference other Foundation modules

Feature Layer

  • Business capabilities and features
  • Examples: Navigation, Search, Forms, Checkout, Articles
  • Each feature is independent and self-contained
  • Can reference Foundation modules only
  • NEVER reference other Feature modules

Project Layer

  • Tenant/site-specific implementations
  • Examples: Website, Corporate, MobileApp
  • Assembles features for specific sites
  • Can reference Feature and Foundation modules

Dependency Rule

Project → Feature → Foundation

Features must NEVER reference other Features. If shared logic is needed, extract to Foundation.

Folder Structure

src/
├── Foundation/
│   └── {Module}/
│       ├── code/
│       │   ├── DependencyInjection/
│       │   ├── Services/
│       │   └── Foundation.{Module}.csproj
│       └── serialization/
│           └── {Module}.module.json
├── Feature/
│   └── {Module}/
│       ├── code/
│       │   ├── Controllers/
│       │   ├── Models/
│       │   ├── Services/
│       │   ├── Repositories/
│       │   └── Feature.{Module}.csproj
│       └── serialization/
│           ├── Templates/
│           ├── Renderings/
│           └── {Module}.module.json
└── Project/
    └── {Site}/
        ├── code/
        │   ├── Layouts/
        │   └── Project.{Site}.csproj
        └── serialization/
            ├── Content/
            └── {Site}.module.json

Dependency Injection

Service Registration

using Microsoft.Extensions.DependencyInjection;
using Sitecore.DependencyInjection;

public class RegisterDependencies : IServicesConfigurator
{
    public void Configure(IServiceCollection services)
    {
        // Transient: New instance each time
        services.AddTransient<ISearchService, SearchService>();

        // Scoped: One instance per HTTP request
        services.AddScoped<INavigationService, NavigationService>();

        // Singleton: One instance for application lifetime
        services.AddSingleton<ICacheService, CacheService>();
    }
}

Config Registration

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <services>
      <configurator type="Foundation.DI.RegisterDependencies, Foundation.DI" />
    </services>
  </sitecore>
</configuration>

Constructor Injection

public class NavigationController : Controller
{
    private readonly INavigationService _navigationService;
    private readonly ILogger<NavigationController> _logger;

    public NavigationController(
        INavigationService navigationService,
        ILogger<NavigationController> logger)
    {
        _navigationService = navigationService;
        _logger = logger;
    }

    public ActionResult Index()
    {
        var datasource = RenderingContext.Current.Rendering.Item;
        if (datasource == null)
        {
            _logger.LogWarning("Navigation datasource is null");
            return new EmptyResult();
        }

        var model = _navigationService.GetNavigation(datasource);
        return View(model);
    }
}

Content Access Patterns

Use Content Search (Recommended)

public class SearchRepository : ISearchRepository
{
    private readonly ISearchIndex _index;

    public SearchRepository(ISearchIndexResolver indexResolver)
    {
        _index = indexResolver.GetIndex("sitecore_web_index");
    }

    public IEnumerable<ArticleSearchResult> GetArticles(
        ID templateId,
        string language,
        int maxResults = 100)
    {
        using (var context = _index.CreateSearchContext())
        {
            return context.GetQueryable<ArticleSearchResult>()
                .Where(x => x.TemplateId == templateId)
                .Where(x => x.Language == language)
                .OrderByDescending(x => x.Created)
                .Take(maxResults)
                .ToList();
        }
    }
}

Avoid Sitecore.Query (Slow)

// BAD - Tree traversal, O(n) where n = tree size
var items = item.Axes.SelectItems(".//*[@@templateid='{GUID}']");

// BAD - Even "fast:" queries traverse the tree
var items = Database.SelectItems("fast:/sitecore/content//*");

// GOOD - Use Content Search instead
var items = searchContext.GetQueryable<SearchResultItem>()
    .Where(x => x.TemplateId == templateId)
    .ToList();

Avoid GetDescendants (Memory Heavy)

// BAD - Loads entire subtree into memory
var allItems = rootItem.GetDescendants().ToList();

// GOOD - Use Content Search with path filter
var items = searchContext.GetQueryable<SearchResultItem>()
    .Where(x => x.Paths.Contains(rootItem.ID))
    .Take(1000)
    .ToList();

Caching

HTML Cache (Rendering Level)

Configure in rendering item or config:

<r>
  <d id="{FE5D7FDF-89C0-4D99-9AA3-B5FBD009C9F3}">
    <r uid="{RENDERING-UID}"
       s:caching="1"
       s:varybydata="1"
       s:varybydevice="0"
       s:varybylogin="0"
       s:varybyuser="0"
       s:varybyquerystirng="0"
       s:timeout="01:00:00" />
  </d>
</r>

Custom Cache

public class NavigationCache : CustomCache
{
    public NavigationCache(string name, long maxSize)
        : base(name, maxSize)
    {
    }

    public NavigationModel Get(string cacheKey)
    {
        return GetObject<NavigationModel>(cacheKey);
    }

    public void Set(string cacheKey, NavigationModel model, TimeSpan duration)
    {
        SetObject(cacheKey, model, duration);
    }
}

Register cache:

public class RegisterCaches : IServicesConfigurator
{
    public void Configure(IServiceCollection services)
    {
        services.AddSingleton<NavigationCache>(provider =>
            new NavigationCache("NavigationCache", StringUtil.ParseSizeString("50MB")));
    }
}

Configuration Patching

Basic Patch

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <settings>
      <setting name="MySetting" value="MyValue" />
    </settings>
  </sitecore>
</configuration>

Environment-Specific Settings

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"
               xmlns:env="http://www.sitecore.net/xmlconfig/env/">
  <sitecore>
    <settings>
      <!-- Only in Production -->
      <setting name="Analytics.Enabled" env:require="Production">
        <patch:attribute name="value">true</patch:attribute>
      </setting>

      <!-- Only in Development -->
      <setting name="Analytics.Enabled" env:require="Development">
        <patch:attribute name="value">false</patch:attribute>
      </setting>
    </settings>
  </sitecore>
</configuration>

Role-Specific Settings

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"
               xmlns:role="http://www.sitecore.net/xmlconfig/role/">
  <sitecore>
    <settings>
      <!-- Only on Content Management servers -->
      <setting name="Preview.Enabled" role:require="ContentManagement">
        <patch:attribute name="value">true</patch:attribute>
      </setting>

      <!-- Only on Content Delivery servers -->
      <setting name="Preview.Enabled" role:require="ContentDelivery">
        <patch:attribute name="value">false</patch:attribute>
      </setting>
    </settings>
  </sitecore>
</configuration>

Pipeline Processors

Custom Processor

public class EnrichContextProcessor : HttpRequestProcessor
{
    public override void Process(HttpRequestArgs args)
    {
        // Early exit for efficiency
        if (Context.Item == null) return;
        if (Context.Site == null) return;
        if (Context.PageMode.IsExperienceEditor) return;

        // Processor logic here
        EnrichContext();
    }

    private void EnrichContext()
    {
        // Custom enrichment logic
    }
}

Config Registration

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="Feature.Context.EnrichContextProcessor, Feature.Context"
                   patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" />
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

Solr Index Configuration

Custom Index

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <contentSearch>
      <configuration>
        <indexes>
          <index id="custom_articles_index">
            <configuration ref="contentSearch/indexConfigurations/defaultSolrIndexConfiguration">
              <documentOptions>
                <fields hint="raw:AddComputedIndexField">
                  <field fieldName="article_author" returnType="string">
                    Feature.Articles.ComputedFields.ArticleAuthor, Feature.Articles
                  </field>
                  <field fieldName="article_category" returnType="stringCollection">
                    Feature.Articles.ComputedFields.ArticleCategories, Feature.Articles
                  </field>
                </fields>
              </documentOptions>
            </configuration>
          </index>
        </indexes>
      </configuration>
    </contentSearch>
  </sitecore>
</configuration>

Computed Field

public class ArticleAuthor : IComputedIndexField
{
    public string FieldName { get; set; }
    public string ReturnType { get; set; }

    public object ComputeFieldValue(IIndexable indexable)
    {
        var item = (indexable as SitecoreIndexableItem)?.Item;
        if (item == null) return null;

        var authorItem = item.GetReferenceField("Author")?.TargetItem;
        return authorItem?["Name"];
    }
}

Sitecore Content Serialization (SCS)

module.json Structure

{
  "namespace": "Feature.Navigation",
  "items": {
    "includes": [
      {
        "name": "templates",
        "path": "/sitecore/templates/Feature/Navigation",
        "allowedPushOperations": "CreateUpdateAndDelete"
      },
      {
        "name": "renderings",
        "path": "/sitecore/layout/Renderings/Feature/Navigation",
        "allowedPushOperations": "CreateUpdateAndDelete"
      },
      {
        "name": "settings",
        "path": "/sitecore/system/Settings/Feature/Navigation",
        "allowedPushOperations": "CreateUpdateAndDelete"
      }
    ]
  }
}

CLI Commands

# Pull items from Sitecore to disk
dotnet sitecore ser pull

# Push items from disk to Sitecore
dotnet sitecore ser push

# Validate serialization
dotnet sitecore ser validate

Glass Mapper Patterns

Model Definition

[SitecoreType(TemplateId = "{GUID}", AutoMap = true)]
public class ArticlePage
{
    [SitecoreId]
    public virtual Guid Id { get; set; }

    [SitecoreField("Title")]
    public virtual string Title { get; set; }

    [SitecoreField("Content")]
    public virtual string Content { get; set; }

    [SitecoreField("Author")]
    public virtual Author Author { get; set; }

    [SitecoreChildren]
    public virtual IEnumerable<RelatedArticle> RelatedArticles { get; set; }
}

Service Usage

public class ArticleService : IArticleService
{
    private readonly ISitecoreService _sitecoreService;

    public ArticleService(ISitecoreService sitecoreService)
    {
        _sitecoreService = sitecoreService;
    }

    public ArticlePage GetArticle(Guid id)
    {
        return _sitecoreService.GetItem<ArticlePage>(id);
    }
}

Avoid N+1 Queries

// BAD - N+1 queries with lazy loading
foreach (var article in articles)
{
    var author = article.Author; // Lazy load for each article
}

// GOOD - Eager load or use Content Search
var articleIds = articles.Select(a => a.Id).ToList();
var authors = searchContext.GetQueryable<AuthorSearchResult>()
    .Where(x => articleIds.Contains(x.ArticleId))
    .ToList();
Weekly Installs
3
GitHub Stars
1
First Seen
Mar 1, 2026
Installed on
opencode3
gemini-cli3
github-copilot3
codex3
amp3
cline3