Sashais a lightweight static site generator built on Pandoc.
Sasha converts your folder of .md Markdown files into a static site.
It ships with a simple templating system based on the {{ }} Mustache syntax. Templates are HTML fragments, with no other requirements.
Generated sites are as lightweight as you make them. Sites can easily be deployed to nearly any service—like GitHub Pages—for free or low-cost.
Built on the Directory → Article folder structure concept. Similar to other Static Site Generators like Jekyll or Gatsby.
- Installation
- Configuration
- Usage
- Basic Concepts
- Folder Structure
- Formatting Articles
- Templates
- Deployment
- Node.js (chokidar, serve) — +19
Note: Node.js is not required for the builder.
- Chokidar CLI — +9.5
-
Install the required dependencies.
The following instructions are for macOS using Homebrew.
CLI dependencies:
brew install bash node pandoc python3
Node services:
npm install -g chokidar-cli prettier serve
-
Install the
SashaCLI into your working directory.build.sh— Static site builder.serve.sh— Local development server.ssg.sh—SashaCLI.
-
Mark the CLI scripts as executable.
chmod +x build.sh server.sh ssg.sh
Windows is not officially supported.
To use Sasha on Windows:
- Install the required dependencies.
- Set the appropriate location for
bashat the top of the CLI scripts. - Mark the CLI scripts as executable.
The Sasha CLI is configured using a ssg.config file.
If no ssg.config file is found, a default one will be created in the current directory.
The easiest way to configure Sasha is to edit the default ssg.config file. It will be created for you in the root of your project when you first run Sasha in a new project.
The domain of your site.
The suffix appended to the title of each page.
Example:
Welcome.md
PAGE_TITLE_SUFFIX=" | SASHA | Static Site Generator"Welcome | SASHA | Static Site Generator
The author of the site, used in meta tags.
The X/Twitter handle of the author, used in meta tags.
Enables formatting via Prettier for the generated HTML.
Purges the entire output directory before building the site.
If your output directory is empty (repo-root output), purge is skipped to avoid deleting your project root.
Enables slugification of the file names of the articles.
Example:
Welcome! Here's My New Site.md
`welcome-here-s-my-new-site`
The filename of the file to output the llm.txt file to.
The llm.txt file is stitched together from all processed articles.
If empty, no llm.txt file will be output.
The default image used for the meta tags in the generated site header, if no header image is found in the article.
This is the image displayed when the page is shared, such as on social media.
The default image used for the article thumbnail in Directory pages, if no header image is found in the article.
If empty, no default article thumbnail is displayed.
The port the development server will run on.
Default: public
The directory containing static assets (images, CSS, media, etc).
This folder is ignored during directory page crawling (so directory index pages won’t list it as a navigable folder).
The Sasha CLI is used to build & serve your site.
It watches the current directory, and automatically rebuilds your site whenever you make changes to your project.
./ssg.sh [config_file] [input_directory] [output_directory] [template_directory] [ignore_file]-
[config_file]Default:
ssg.configThe
Sashaconfiguration file.See Configuration for a guide to setting up your config file.
-
[input_directory]Default:
markdownThe directory containing your Markdown articles.
-
[output_directory]Default:
portfolioThe directory where your generated site will be saved.
If set to an empty string (
""), output will be written to the project root (e.g.design/...,engineering/...). -
[template_directory]Default:
templatesThe directory containing your HTML template fragments.
-
[ignore_file](Optional)Default:
.ssgignoreThe ignore file containing the list of files ignored by the watcher.
Example:
.DS_Store .gitignore .nojekyll CNAME
The Sasha Builder can be run manually to build your site.
./build.sh [config_file] [input_directory] [output_directory] [template_directory]-
[config_file]Default:
ssg.configThe
Sashaconfiguration file.See Configuration for a guide to setting up your config file.
-
[input_directory]Default:
markdownThe directory containing your Markdown articles.
-
[output_directory]Default:
buildThe directory where your generated site will be saved.
If set to an empty string (
""), output will be written to the project root (e.g.design/...,engineering/...). -
[template_directory]Default:
templatesThe directory containing your HTML template fragments.
The development server can be run independently to only serve your site.
./server.sh [port]-
[port]Default:
3000The port the development server will run on.
Sasha is purposefully simple.
Articles & directories are rendered into templates.
An Article is a .md Markdown formatted text file.
A Directory is a folder containing Article files, or other Directory folders.
Both Article files & Directory folders are rendered as pages in your site.
Directory pages display Article files and other Directory subfolders.
Directory crawling ignores:
- Your Markdown input directory (e.g.
markdown/) - Your public assets directory (e.g.
public/, configurable viaPUBLIC_DIRECTORY) - Your template fragments directory (e.g.
templates/) - Paths matched by
.gitignore - Paths matched by
.ssgignore(if present; also supported as..ssgignore)
An Article page displays the content of its associated .md Markdown file.
A Template is a .frag.html HTML fragment.
{{ }} Tags are used to insert content & other HTML fragments into a Template.
Sasha uses your Article folder structure to generate your site structure.
There are no restrictions on the structure of your Article folder provided to the CLI.
The structure of your generated site will mirror the structure of your Article folder.
Your folder of Template fragments will contain default templates applied across your site.
You can override templates by mirroring the folder structure of your Article files.
For example, to override the design Directory page template:
├── markdown
│ └── design
│ ├── README.md
│ └── SASHA.md
└── templates
├── design
│ └── directory.frag.html
│ ...├── markdown
│ ├── design
│ │ ├── Render–Optimized Skeumorphic UI, 2022.md
│ │ └── Website Redesign, 2023.md
│ └── engineering
│ ├── Static Site Generator.md
│ └── OpenGL 2.1 Pipeline Framework.md
└── templates
├── design
│ └── directory.frag.html
├── head.frag.html
├── body.frag.html
├── directory.frag.html
├── layout.frag.html
└── footer.frag.htmlArticles are formatted using Markdown, and rendered using Pandoc.
Technically, any markup language supported by Pandoc can be used. Markdown is currently the only format supported & verified working with Sasha.
Articles contain additional formatting features to extend functionality & enhance presentation.
The title of an Article is the filename of the file, without the file extension.
It will be displayed as the title of the article listing on Directory pages.
Example:
Case Study: Light & Shadows, 2025.md
Case Study: Light & Shadows, 2025
The header image of an Article is the first  image in the article.
It will be displayed as the thumbnail for the Article listing on Directory pages, as well as for meta tags when sharing the article.
The description of an Article is the first paragraph matched, following the Article Header Image.
It will be displayed as the description for the Article listing on Directory pages, as well as for meta tags when sharing the article.
You can add a category to an Article by adding a > Blockquote to the line directly before the article's first # Heading.
Example:
> Case Study
# Light & Shadows
Sasha supports a few components using special Markdown syntax.
Links can be embedded as downloadable links directly inside articles using a link and display lablel.
The download-link.frag.html Download Link Component HTML Template is used to render the parsed download link.
Example:
+[View My Resume](/public/resume.pdf "Download Alfred R. Duarte's Resume")
Emails can be automatically transformed into a component.
If no email-link.frag.html Email Link Component HTML Template is found, the email will be displayed as a normal link.
Example:
[Email Me](mailto:alfred.r.duarte@gmail.com "Email Alfred R. Duarte")
Profile links can be embedded as bare linked images using an icon link, URL, and accessible title.
The social-link.frag.html Social Link Component HTML Template is used to render the parsed social link.
Example:
>[/public/icons/linkedin.svg](https://www.linkedin.com/in/alfredrd/ "LinkedIn - Alfred R. Duarte")
iFrames can be embedded directly inside articles using a link and a display size.
The iframe.frag.html iFrame Component HTML Template is used to render the parsed iFrame.
Example:
@[100%](https://www.wikipedia.org/)
Up to 2 images can be compared in a single container using the %[alt](link) syntax.
Images are separated by a single line break.
The img-compare.frag.html Image Comparison Component HTML Template is used to render the parsed image comparison.
Example:
%[Before](/image1.png)
%[After](/image2.png)
Videos can be embedded directly inside articles using a link, video attributes, and a media type.
The video.frag.html Video Component HTML Template is used to render the parsed video.
Example:
~[autoplay muted loop playsinline width="1080"](/video.mp4 "video/mp4")
::seo-content[label] Hidden SEO Content
Sections can be wrapped as visually hidden, crawlable content using opening and closing block markers.
The block is rendered with the same hidden behavior used by breadcrumbs and related work. Links inside the block are automatically removed from the tab order.
Example:
::seo-content[Canonical biography]
# Alfred R. Duarte
Founder, CEO, Principal Engineer & Designer
Alfred R. Duarte is a founder, principal engineer, and designer.
::/seo-content
Article metadata can be given an explicit description without adding visible page content.
Example:
<!-- ssg:description Alfred R. Duarte is a founder, principal engineer, and designer. -->
You can prefix special symbols to the start of the filename of an Article to change its rendering behavior.
-
*PinnedPlace the article in the list of pinned articles.
Pinned articles are rendered in a separate list than regular articles.
-
~HiddenHide the article from the
Directorypage.The article will still be rendered as a page and can be linked to directly.
-
_Not RenderedThe article will not be rendered. It will not be present in your site.
Example:
- Pinned Article.md
~ Hidden Article.md
_ Not Rendered.md
Templates are .frag.html HTML fragments, with no other requirements.
Templates can contain {{ }} replacement tags. These tags are replaced when an Article is rendered.
Both Article & Directory pages are rendered into the layout.frag.html template.
body.frag.htmldownload-link.frag.htmlemail-link.frag.htmliframe.frag.htmlimg-compare.frag.htmlvideo.frag.html
directory.frag.htmldirectory-crumb.frag.htmllisting-article.frag.htmllisting-pinned.frag.htmllisting-folder.frag.html
Layout templates are used to render both Article & Directory pages.
Some {{ }} tags can be used across all fragments injected into the main layout template.
Select replacement tags are included with each template description below.
The layout template is the main template that all other fragments are rendered into.
-
{{ HEAD }}The contents of the
head.frag.htmlHead Template. -
{{ BODY }}The contents of the template rendered for the page.
For
Articlepages, thebody.frag.htmlBody Template is used to render the contents.For
Directorypages, thedirectory.frag.htmlDirectory Template is used to render the contents. -
{{ FOOTER }}The contents of the
footer.frag.htmlFooter Template.
The head template is used to render the page <head>.
This includes the <title> tag & <meta> tags.
-
{{ PAGE_TITLE }}The title of the page.
Constructed from page title and the
PAGE_TITLE_SUFFIXin thessg.configfile.For
Articlepages, the filename of the article is used as the page title.For
Directorypages, the name of the folder is used as the page title. -
{{ DESCRIPTION }}The description of the page.
-
{{ AUTHOR }}The author of the site.
Extracted from the
ssg.configfile. -
{{ URL }}The canonical URL of the page.
Constructed from the path to the
Article/Directoryand theDOMAINfrom thessg.configfile. -
{{ IMAGE }}The URL of the header image of the article.
If no header image is found, the
DEFAULT_ARTICLE_IMAGEfrom thessg.configfile is used. -
{{ TWITTER_HANDLE }}The X/Twitter handle of the author.
Extracted from the
ssg.configfile.
The footer template is used to render the page <footer>.
-
{{ YEAR }}The current year.
The body template is used to render the Article contents into the page <body>.
-
{{ TITLE }}The title of the article.
Extracted from the article filename.
-
{{ MARKDOWN }}The content of the
.mdMarkdown article rendered as HTML.
The download link template is used to render the +[alt](url "title") Download Link component.
-
{{ DOWNLOAD_LINK_ALT }}The display label for the download link.
-
{{ DOWNLOAD_LINK_SRC }}The
srcattribute of the download link. -
{{ DOWNLOAD_LINK_TITLE }}The
titleattribute of the download link.This is used for the text displayed when hovering over the link.
The email link template is used to render the [alt](mailto:link "title") Email Link component.
-
{{ EMAIL_LINK_ALT }}The display label for the email link.
-
{{ EMAIL_LINK_SRC }}The
srcattribute of the email link.This does not include the
mailto:prefix. -
{{ EMAIL_LINK_TITLE }}The
titleattribute of the email link.
The social link template is used to render the >[icon link](url "title") Social Link component.
-
{{ SOCIAL_LINK_ICON_SRC }}The image or icon
srcused by the social link. -
{{ SOCIAL_LINK_SRC }}The
hrefattribute of the social link. -
{{ SOCIAL_LINK_TITLE }}The accessible label and
titleattribute of the social link.
The iFrame template is used to render the @[size](link) iFrame component.
-
{{ IFRAME_HEIGHT }}The height of the iFrame.
-
{{ IFRAME_SRC }}The
srcattribute of the iFrame.
The image comparison template is used to render the %[alt](link) Image Comparison component.
-
{{ IMG_COMPARE_ALT1 }}The
altattribute for the first image in the comparison. -
{{ IMG_COMPARE_SRC1 }}The
srcattribute for the first image in the comparison. -
{{ IMG_COMPARE_ALT2 }}The
altattribute for the second image in the comparison. -
{{ IMG_COMPARE_SRC2 }}The
srcattribute for the second image in the comparison.
The video template is used to render the ~[attributes](link "type") Video component.
-
{{ VIDEO_ATTRIBUTES }}The attributes for the video.
-
{{ VIDEO_SOURCE }}The
srcattribute of the video. -
{{ VIDEO_TYPE }}The
typeattribute of the video.
The directory template is used to render the contents of the Directory into the page <body>.
-
{{ CRUMBS }}The breadcrumbs for the directory.
Assembled from a list of directory crumb templates.
-
{{ PARENT_DIRECTORY }}The Folder Listing for the parent directory.
Used to navigate back to the parent directory.
-
{{ FOLDERS }}The Folder Listings for all subdirectories.
-
{{ PINNED_ARTICLES }}The Pinned Article Listings for the directory.
-
{{ HIDDEN_ARTICLES }}The Hidden Article Listings for the directory.
-
{{ ARTICLES }}The Article Listings for the directory.
The directory crumb template is used to render a single, individual link in the directory breadcrumb.
The completed breadcrumb is assembled as a list of directory crumb templates.
-
{{ DIRECTORY_HREF }}The URL of the directory.
-
{{ DIRECTORY_NAME }}The name of the directory.
Extracted from the directory folder name.
The article listing template is used to render Article listings inside Directory pages.
-
{{ ARTICLE_HREF }}The URL of the article.
-
{{ ARTICLE_TITLE }}The title of the article.
Extracted from the article filename.
-
{{ ARTICLE_DESCRIPTION }}The description of the article.
Extracted as the first paragraph following the header image.
-
{{ ARTICLE_IMAGE }}The URL of the header image of the article.
The pinned article listing template is used to render * Pinned Article listings inside Directory pages.
-
{{ ARTICLE_HREF }}The URL of the article.
-
{{ ARTICLE_TITLE }}The title of the article.
Extracted from the article filename.
-
{{ ARTICLE_DESCRIPTION }}The description of the article.
Extracted as the first paragraph following the header image.
-
{{ ARTICLE_IMAGE }}The URL of the header image of the article.
The folder listing template is used to render Directory listings inside Directory pages.
-
{{ ARTICLE_HREF }}The URL of the directory.
-
{{ ARTICLE_TITLE }}The title of the directory.
Extracted from the directory filename.
-
{{ ARTICLE_IMAGE }}The URL of the header image of the directory.
Netlify Drop is a simple way to deploy your site to Netlify.
-
Navigate to Netlify Drop.
-
Drag your project folder into the browser window.
Netlify will automatically build & deploy your site.
Your site is live at the URL provided by Netlify.
Using a public GitHub repository, you can deploy your site to GitHub Pages for free.
You automatically deploy new site builds each time you push to the repository.
-
Initialize a new repository in your project.
git init
-
Add a
.nojekyllfile to the root of your project, with the following content:touch .nojekyllThis prevents GitHub from processing the project using Jekyll.
-
Create a new repository on GitHub.
-
Push your project to the repository.
-
Enable GitHub Pages for the repository.
https://github.com/{USERNAME}/{REPOSITORY}/settings/pages
- Select the branch to publish. Default is
main. - Click Save.
- Select the branch to publish. Default is
-
Your site is live at the URL provided by GitHub.
- https://{USERNAME}.github.io/{REPOSITORY}
-
Update the
DOMAINin thessg.configfile to match your GitHub Pages URL.DOMAIN="https://{USERNAME}.github.io/{REPOSITORY}"This is used to generate correct canonical URLs across your site build.
-
Rebuild your site & push to the repository.
-
(Optional) To use a custom domain, follow the instructions provided by GitHub.
Update the
DOMAINin thessg.configfile to match your custom domain.
