Theme Configuration
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' | asset_url | stylesheet_tag }}
<!-- Canonical URL -->
<link rel="canonical" href="{{ request.url }}">
<!-- Language alternates -->
{% for translation in page.translations %}
<link rel="alternate" hreflang="{{ translation.locale }}" href="{{ translation.url }}">
{% endfor %}
</head>
<body class="template-{{ template }}">
{% include 'header' %}
<main id="main-content" class="main-content">
{{ content_for_body }}
</main>
{% include 'footer' %}
<!-- JavaScript -->
{{ 'theme.js' | asset_url | script_tag }}
</body>
</html>Specialized Layouts
layouts/checkout.liquid - Minimal layout for checkout:
<!DOCTYPE html>
<html lang="{{ locale }}">
<head>
<title>Checkout - {{ site.name }}</title>
{{ 'checkout.css' | asset_url | stylesheet_tag }}
</head>
<body class="checkout-page">
<header class="checkout-header">
<a href="/">{{ site.name }}</a>
</header>
{{ content_for_body }}
{{ 'checkout.js' | asset_url | script_tag }}
</body>
</html>Template Organization
Naming Conventions
index.liquid- Homepagepage.liquid- Generic pagesarticle.liquid- Blog articlesproduct.liquid- Product pagescollection.liquid- Product collectionscustomer.liquid- Customer account404.liquid- Not found page
Template Routing
Templates are automatically routed:
| URL | Template |
|---|---|
/ | index.liquid |
/about | page.liquid |
/blog/article-slug | article.liquid |
/products/product-slug | product.liquid |
/collections/collection-slug | collection.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
├── [email protected]
├── 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' | asset_url | stylesheet_tag }}
{{ 'theme.min.js' | asset_url | script_tag, defer: true }}
<!-- ⚠️ Development only -->
{{ 'theme.css' | asset_url | stylesheet_tag }}
{{ 'theme.js' | asset_url | script_tag }}Next Steps
- Performance - Optimization strategies
- Multilingual - Translation setup
- Assets & CDN - Asset management
- Global Variables - Available drops
Build well-organized, maintainable Nimbu themes!