neosite
v0.1.1
Published
Static, section-based site builder that includes a drag and drop visual editor. Liquid syntax.
Readme
Neosite
A tiny static site generator built on LiquidJS. No config needed. Just layouts, partials, and pages.
Directory structure
my-site/
├── layouts/
│ ├── layout.html ← shared HTML shell
│ ├── layout_header.html ← optional header fragment (supports {% schema %})
│ └── layout_footer.html ← optional footer fragment (supports {% schema %})
├── partials/
│ ├── hero.html ← reusable sections with optional {% schema %}
│ └── cta.html
├── content/
│ ├── index.html ← becomes dist/index.html
│ ├── about.html ← becomes dist/about.html
│ └── blog/
│ └── post.html ← becomes dist/blog/post.html
├── settings.json ← site config (required for build/serve)
└── package.jsonInstallation
npm install neositeUsage
CLI
neosite new [path] # creates a new project in <path>
neosite build <path> # builds site (path defaults to cwd)
neosite serve <path> # opens editor UI localhost:3000 (path defaults to cwd)Commands can also be run via npm run
Programmatic
import { NeoSite } from "./lib/index.js";
const site = new NeoSite({
layoutsDir: "layouts",
partialsDir: "partials",
contentDir: "content",
outputDir: "dist",
settingsFile: "settings.json",
});
await site.init();
await site.build();Concepts
Layout (layouts/layout.html)
The layout wraps every page. Use {% render_content %} where the page body and sections should go.
You can also use {% partial %} tags and any frontmatter variables from the page.
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<header>{% render_header %}</header>
<main>{% render_content %}</main>
<footer>{% render_footer %}</footer>
</body>
</html>Layout Fragments (layouts/layout_header.html, layouts/layout_footer.html)
Header and footer fragments live in layoutsDir (not in partialsDir).
They support {% schema %} blocks for configurable options and are rendered
via {% render_header %} and {% render_footer %} tags.
Defining a header (layouts/layout_header.html):
{% schema %}
{
"siteName": { "type": "string", "default": "My Site" },
"showNav": { "type": "boolean", "default": true }
}
{% endschema %}
<header>
<h1>{{ siteName }}</h1>
{% if showNav %}
<nav><!-- navigation --></nav>
{% endif %}
</header>When a fragment is not present, the corresponding {% render_* %} tag outputs nothing.
Pages (content/*.html)
Each page has an optional {% schema %} block (JSON) at the top.
Variables defined there are available in the page template and passed to the layout.
{% schema %}
{
"title": "Home",
"description": "Welcome to Acme."
}
{% endschema %}
<h1>Hello world</h1>
{% partial "cta", heading: "Get started today", theme: "dark" %}Override the layout per-page with "layout": "alt-layout.html" in the schema.
Partials (partials/*.html)
Partials are reusable HTML fragments. Each one can declare a {% schema %} block
with default variable values. When you include a partial you can override any
subset of those variables inline.
Defining a partial (partials/cta.html):
{% schema %}
{
"heading": "Ready to get started?",
"subheading": "Join thousands of happy customers.",
"buttonLabel": "Get Started",
"buttonUrl": "/signup",
"theme": "light"
}
{% endschema %}
<section class="cta cta--{{ theme }}">
<h2>{{ heading }}</h2>
{% if subheading %}<p>{{ subheading }}</p>{% endif %}
<a class="btn" href="{{ buttonUrl }}">{{ buttonLabel }}</a>
</section>Using a partial (in a page or layout):
{% partial "cta" %} {# all defaults #}
{% partial "cta", heading: "Ship it today", theme: "dark" %} {# override #}
{% partial "header", siteName: siteName %} {# from context #}Inline argument syntax:
key: "string value"— literal string (double or single quotes)key: variableName— resolved from current template context
Variables not overridden fall back to the partial's schema defaults.
Nested partials
Partials can include other partials:
{% partial "icon", name: "arrow" %}Settings (settings.json)
Site config maps URL paths to sections. Header and footer are configured per-page
using header and footer entries.
{
"/": {
"header": { "siteName": "Acme", "showNav": true },
"footer": { "copyright": "2024 Acme Inc." },
"sections": [
{ "type": "hero", "options": { "heading": "Welcome" } },
{ "type": "cta", "options": { "theme": "dark" } }
]
},
"/about": {
"sections": [
{ "type": "content" }
]
}
}Section types:
"type": "content"— renders the page's HTML content at that position"type": "partial-name"— renders a partial with given options"hidden": true— skips rendering that section/header/footer
Pages without content files can render via settings.json section definitions alone.
Schema format
Two formats are accepted in {% schema %} blocks:
Flat (simple defaults):
{ "title": "Home", "count": 3 }Typed (with validation):
{
"title": { "type": "string", "default": "Home", "required": true },
"theme": { "type": "string", "default": "light", "options": ["light", "dark"] }
}Supported types: string, number, boolean, array, object.
Editor / Dev Server
Running npm run serve starts a dev server on http://localhost:3000 with:
- Visual editor for pages and sections
- Live preview at
/preview/{page-url} - API endpoints:
GET /api/pages— list pagesGET /api/sections— list available section types (includes__headerand__footer)GET /api/page/{url}— get page contentPUT /api/settings— update settingsPOST /api/render— render a pagePOST /api/build— rebuild site
Example
Run the bundled example:
cd example
../bin/cli.js build
# -> dist/index.html
# -> dist/about.html
# -> dist/contact.html