Theme Configuration
Theme structure, configuration files, versioning, and asset organization
Learn how to structure, configure, and organize your Nimbu themes for maintainability and performance.
Theme Directory Structure
theme-name/
├── nimbu.yml # Toolbelt configuration (site, theme, apps)
├── nimbu.js # Build configuration (webpack, CDN)
├── layouts/
│ ├── default.liquid # Default layout
│ ├── checkout.liquid # Checkout layout
│ └── account.liquid # Customer account layout
├── templates/
│ ├── index.liquid # Homepage
│ ├── page.liquid # Page template
│ ├── article.liquid # Article template
│ ├── product.liquid # Product template
│ └── collection.liquid # Collection template
├── snippets/
│ ├── header.liquid # Header component
│ ├── footer.liquid # Footer component
│ ├── product-card.liquid # Product card component
│ └── navigation.liquid # Navigation component
├── stylesheets/
│ ├── theme.css # Main stylesheet
│ └── print.css # Print stylesheet
├── javascripts/
│ ├── theme.js # Main JavaScript
│ └── vendor.js # Third-party libraries
├── images/
│ ├── logo.png
│ └── placeholder.jpg
├── fonts/
│ ├── custom-font.woff2 # Custom web fonts
│ └── custom-font.woff
├── src/ # Source files (if using build tools)
│ ├── index.ts
│ └── index.css
└── README.md # Theme documentationConfiguration Files
nimbu.yml
The nimbu.yml file configures the Nimbu toolbelt for your theme.
Basic Example:
---
site: mysite
theme: default-themeAdvanced Example with Cloud Code Apps:
theme: default-theme
site: mysite
apps:
- name: production
id: 84acd4b8941e722e34e1c6b36bd40137
dir: code/dist
glob: '*.js'
- name: staging
id: 7b7d14488c5d7eea0ff108c59c020882
dir: code/dist
glob: '*.js'
host: api.nimbu.beConfiguration Options:
site(required) - Site subdomaintheme(required) - Theme nameapps(optional) - Cloud code app configurationsname- Environment name (e.g., production, staging)id- Cloud code app IDdir- Directory containing compiled codeglob- File pattern to uploadhost- API host (optional, defaults to api.nimbu.io)
nimbu.js
The nimbu.js file configures the Nimbu build toolbelt for webpack compilation and asset management.
Basic Example:
process.env.GENERATE_SOURCEMAP = 'false';
module.exports = {
REACT: false,
CDN_ROOT: '../', // Put the root url of the CDN here
WEBPACK_ENTRY: {
app: ['./src/index.ts', './src/index.css'],
},
};Advanced Example with Environment Configuration:
const initNimbuEnv = require('./nimbu_env');
const execa = require('execa');
// Git-based versioning
const gitHash = execa.sync('git', ['rev-parse', '--short', 'HEAD']).stdout;
const gitNumCommits = Number(execa.sync('git', ['rev-list', 'HEAD', '--count']).stdout);
const gitDirty = execa.sync('git', ['status', '-s', '-uall']).stdout.length > 0;
process.env.__VERSION__ = `${gitHash}-${gitNumCommits}-${gitDirty}-${new Date().getTime()}`;
process.env.GENERATE_SOURCEMAP = 'true';
module.exports = initNimbuEnv().then(() => {
console.log(`Connecting to: ${process.env.NIMBU_ENV}`);
return {
REACT: true,
BUGSNAG_API_KEY: 'your-bugsnag-key',
GOOGLE_MAPS_KEY: 'your-google-maps-key',
GOOGLE_ANALYTICS_KEY:
process.env.NIMBU_ENV === 'production' ? 'UA-PROD' : 'UA-STAGING',
CDN_ROOT:
process.env.NIMBU_ENV === 'production'
? 'https://cdn.nimbu.io/s/xxx/themes/xxx'
: 'https://cdn.nimbu.io/s/xxx/themes/xxx',
SVG_LOADER_INCLUDE: [/src\/assets\/svg-icons\//],
SVG_LOADER_OPTIONS: {
plugins: [{ removeViewBox: false }],
},
NIMBU_SITE: process.env.NIMBU_ENV === 'staging' ? 'mysite-staging' : 'mysite',
WEBPACK_ENTRY: {
app: ['./src/app.scss', './src/app.js'],
admin: ['./src/admin.scss', './src/admin.js'],
},
MEMORY_LIMIT: 4096,
};
});Configuration Options:
REACT- Enable React support (boolean)CDN_ROOT- CDN URL for assets (string)WEBPACK_ENTRY- Webpack entry points (object)BUGSNAG_API_KEY- Bugsnag error tracking keyGOOGLE_MAPS_KEY- Google Maps API keyGOOGLE_ANALYTICS_KEY- Google Analytics tracking IDSVG_LOADER_INCLUDE- Paths for SVG loader (array)SVG_LOADER_OPTIONS- SVGO plugin options (object)NIMBU_SITE- Site subdomain for environmentMEMORY_LIMIT- Node memory limit in MB (number)
Async Configuration:
The nimbu.js file can return a Promise for async environment setup:
module.exports = initNimbuEnv().then(() => {
return {
// ... configuration based on process.env.NIMBU_ENV
};
});Layout Organization
Default Layout
layouts/default.liquid:
<!DOCTYPE html>
<html lang="{{ locale }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ page.title }} - {{ site.name }}</title>
{% if page.meta_description %}
<meta name="description" content="{{ page.meta_description }}">
{% else %}
<meta name="description" content="{{ site.meta_description }}">
{% endif %}
<!-- Stylesheets -->
{{ 'theme.css' | stylesheet_tag }}
<!-- CSRF, canonical and hreflang links, robots directives -->
{{ content_for_header }}
</head>
<body class="template-{{ template }}">
{% include 'header' %}
<main id="main-content" class="main-content">
{{ content_for_body }}
</main>
{% include 'footer' %}
<!-- JavaScript -->
{{ 'theme.js' | javascript_tag }}
</body>
</html>{{ content_for_header }} already outputs the canonical link and the hreflang alternates for every visible locale (see SEO). Don't add your own, or pages end up with duplicate or conflicting annotations.
Specialized Layouts
layouts/checkout.liquid - Minimal layout for checkout:
<!DOCTYPE html>
<html lang="{{ locale }}">
<head>
<title>Checkout - {{ site.name }}</title>
{{ 'checkout.css' | stylesheet_tag }}
{{ content_for_header }}
</head>
<body class="checkout-page">
<header class="checkout-header">
<a href="/">{{ site.name }}</a>
</header>
{{ content_for_body }}
{{ 'checkout.js' | javascript_tag }}
</body>
</html>Template Organization
Naming Conventions
index.liquid- Homepagepage.liquid- Generic pagesblog.liquid,article.liquid- Blog listing and articlesshop/product.liquid,shop/collection.liquid- Product and collection pagescustomers/login.liquid,customers/register.liquid,customers/account.liquid- Customer pagessearch.liquid- Search results404.liquid- Optional themed not-found page for unknown pages, products and collections (see Themed 404 Page)robots.txt.liquid- Optional extra robots.txt rules (see SEO)
See Template Types for the full list.
Template Routing
Templates are automatically routed:
| URL | Template |
|---|---|
/ | index.liquid |
/about | page.liquid |
/blog/article-slug | article.liquid |
/products/product-slug | shop/product.liquid |
/shop/collection-slug | shop/collection.liquid |
/search?q=... | search.liquid |
Snippet Organization
Component Naming
snippets/
├── components/
│ ├── button.liquid
│ ├── card.liquid
│ └── modal.liquid
├── sections/
│ ├── hero.liquid
│ ├── features.liquid
│ └── testimonials.liquid
├── partials/
│ ├── header.liquid
│ ├── footer.liquid
│ └── navigation.liquid
└── utils/
├── social-share.liquid
└── breadcrumbs.liquidSnippet Conventions
snippets/product-card.liquid:
{% comment %}
Product Card Component
Parameters:
- product: Product object (required)
- show_vendor: Boolean (optional, default: false)
- image_size: String (optional, default: '400px')
{% endcomment %}
<div class="product-card">
<a href="{{ product.url }}" class="product-link">
{% if product.images.first %}
<img
src="{{ product.images.first.url | filter, width: image_size | default: '400px' }}"
alt="{{ product.name }}"
loading="lazy">
{% endif %}
</a>
<h3 class="product-title">
<a href="{{ product.url }}">{{ product.name }}</a>
</h3>
{% if show_vendor and product.vendor %}
<p class="product-vendor">{{ product.vendor }}</p>
{% endif %}
<p class="product-price">
{{ product.price | money_with_currency }}
</p>
</div>Asset Organization
CSS Architecture
stylesheets/
├── base/
│ ├── reset.css
│ ├── typography.css
│ └── variables.css
├── components/
│ ├── buttons.css
│ ├── forms.css
│ └── cards.css
├── layouts/
│ ├── header.css
│ ├── footer.css
│ └── grid.css
├── pages/
│ ├── homepage.css
│ ├── product.css
│ └── checkout.css
└── theme.css # Main import filestylesheets/theme.css:
/* Base */
@import "base/reset.css";
@import "base/variables.css";
@import "base/typography.css";
/* Components */
@import "components/buttons.css";
@import "components/forms.css";
@import "components/cards.css";
/* Layouts */
@import "layouts/header.css";
@import "layouts/footer.css";
@import "layouts/grid.css";
/* Pages */
@import "pages/homepage.css";
@import "pages/product.css";JavaScript Organization
javascripts/
├── modules/
│ ├── cart.js
│ ├── product.js
│ └── search.js
├── utils/
│ ├── helpers.js
│ └── ajax.js
├── vendor/
│ ├── swiper.js
│ └── alpine.js
└── theme.js # Main entry pointjavascripts/theme.js:
// Utilities
import { debounce, throttle } from './utils/helpers.js';
import { fetchJSON } from './utils/ajax.js';
// Modules
import './modules/cart.js';
import './modules/product.js';
import './modules/search.js';
// Initialize
document.addEventListener('DOMContentLoaded', function() {
console.log('Theme initialized');
});Images Organization
images/
├── logo.png
├── logo@2x.png
├── favicon.ico
├── placeholder.jpg
└── icons/
├── cart.svg
├── search.svg
└── user.svgFonts Organization
fonts/
├── custom-font-regular.woff2
├── custom-font-regular.woff
├── custom-font-bold.woff2
└── custom-font-bold.woffUsing custom fonts in CSS:
@font-face {
font-family: 'Custom Font';
src: url('/fonts/custom-font-regular.woff2') format('woff2'),
url('/fonts/custom-font-regular.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Custom Font';
src: url('/fonts/custom-font-bold.woff2') format('woff2'),
url('/fonts/custom-font-bold.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}
body {
font-family: 'Custom Font', sans-serif;
}Version Management
Versioning Strategy
Use semantic versioning (MAJOR.MINOR.PATCH):
- MAJOR: Breaking changes
- MINOR: New features (backward compatible)
- PATCH: Bug fixes
Track versions in your package.json or README.md:
package.json:
{
"name": "theme-name",
"version": "2.1.3"
}Changelog
Maintain a CHANGELOG.md:
# Changelog
## [2.1.3] - 2025-10-07
### Fixed
- Product image lazy loading on mobile
- Cart total calculation with discounts
## [2.1.0] - 2025-09-15
### Added
- Quick view modal for products
- Wishlist functionality
- AJAX cart updates
### Changed
- Improved mobile navigation
- Updated checkout flow
## [2.0.0] - 2025-08-01
### Breaking Changes
- New template structure
- Updated Liquid syntax
### Added
- Multi-currency support
- Advanced filteringBest Practices
1. Modular Organization
<!-- ✅ Good: Modular components -->
{% include 'components/product-card', product: product %}
{% include 'sections/hero' %}
<!-- ❌ Bad: Monolithic templates -->
<div class="product-card">
<!-- Hundreds of lines of code -->
</div>2. Consistent Naming
<!-- ✅ Good: Clear naming -->
snippets/product-card.liquid
snippets/collection-filters.liquid
snippets/checkout-summary.liquid
<!-- ❌ Bad: Unclear naming -->
snippets/comp1.liquid
snippets/helper.liquid3. Documentation
Add comments to complex logic:
{% comment %}
Calculate dynamic pricing based on quantity tiers:
- 1-10 items: regular price
- 11-50 items: 10% discount
- 51+ items: 20% discount
{% endcomment %}
{% if cart.items.size > 50 %}
{% assign discount = 0.20 %}
{% elsif cart.items.size > 10 %}
{% assign discount = 0.10 %}
{% else %}
{% assign discount = 0 %}
{% endif %}4. Asset Optimization
<!-- ✅ Good: Optimized assets -->
{{ 'theme.min.css' | stylesheet_tag }}
{{ 'theme.min.js' | javascript_tag: defer: true }}
<!-- ⚠️ Development only -->
{{ 'theme.css' | stylesheet_tag }}
{{ 'theme.js' | javascript_tag }}Next Steps
- Performance - Optimization strategies
- Multilingual - Translation setup
- Assets & CDN - Asset management
- Global Variables - Available drops
Build well-organized, maintainable Nimbu themes!