Assets and Static Files
Templates render the HTML. Assets — CSS, JavaScript, fonts, favicons, downloads — provide the styling, interactivity, and supporting files that templates pull in. SleekCMS organizes assets into a few well-known folders and gives templates two helpers, link() and script(), for including them on a page.
This page covers where assets live, how to include them, the special handling for Tailwind CSS, and how the build pipeline handles static files at deploy time.
Folder Layout
Assets are organized by type:
css/ Stylesheets — included via link()
tailwind.css Special: auto-compiled and auto-injected (see below)
styles.css Your own CSS file(s)
js/ Client-side scripts — included via script()
app.js
carousel.js
public/ Other static files served verbatim at the site root
favicon.ico
apple-touch-icon.png
fonts/inter-var.woff2
downloads/brochure.pdf
robots.txt
The css/ and js/ folders are conventional locations for stylesheets and scripts that templates include. The public/ folder is for any other static file that needs to ship with the site — favicons, manifest files, fonts, downloadables, robots.txt, sitemap.xml, anything that's served as-is at a known URL.
At deploy time, every file under these folders ends up in the deployed output. The directory structure is preserved.
Including CSS
Use the link() helper to add a stylesheet. Pass the path; the helper figures out the right <link> tag attributes:
<% link('/css/styles.css') %>
<!-- Output: <link rel="stylesheet" type="text/css" href="/css/styles.css"> -->
For non-CSS link types (canonical, preconnect, RSS feed autodiscovery), pass an object with explicit attributes:
<% link({ rel: 'canonical', href: url(item) }) %>
<% link({ rel: 'preconnect', href: 'https://fonts.googleapis.com' }) %>
<% link({ rel: 'alternate', type: 'application/rss+xml', title: 'RSS', href: '/rss.xml' }) %>
Don't write raw <link> tags. The injection helpers deduplicate (so calling the same stylesheet from many block templates is safe) and place tags in the right <head> order.
Including JavaScript
Use the script() helper. A path ending in .js becomes an external script; anything else is treated as inline code:
<!-- External -->
<% script('/js/app.js') %>
<!-- Output: <script src="/js/app.js"></script> -->
<!-- Inline -->
<% script('console.log("loaded")') %>
<!-- Output: <script>console.log("loaded")</script> -->
<!-- With explicit attributes -->
<% script({ src: '/js/app.js', defer: true }) %>
<% script({ src: '/js/module.js', type: 'module' }) %>
Scripts are appended at the end of <body> rather than in <head>, which is the right place for most page scripts (no render-blocking).
Tailwind Is Special
/css/tailwind.css is the one stylesheet that does not need to be included via link(). If that file exists, the build pipeline compiles Tailwind utility classes used in your templates and auto-injects the resulting stylesheet on every page. You don't call link('/css/tailwind.css') — doing so would add the file twice.
For everything you'd customize in a Tailwind project — theme tokens, custom utilities, plugin imports — edit /css/tailwind.css directly. The compile pipeline picks up the changes on save.
Files in public/
Anything you drop under public/ is served at the corresponding URL. A file at public/favicon.ico is available at /favicon.ico. A file at public/fonts/inter.woff2 is at /fonts/inter.woff2. A file at public/downloads/menu.pdf is at /downloads/menu.pdf.
You don't need a helper to reference these — write the URL directly in your templates:
<% link({ rel: 'icon', href: '/favicon.ico' }) %>
<style>
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-var.woff2') format('woff2-variations');
}
</style>
<a href="/downloads/menu.pdf">Download menu (PDF)</a>
Use public/ for:
- Favicons (
favicon.ico,apple-touch-icon.png) - PWA manifests (
manifest.json) - Self-hosted fonts (
.woff2) - Static downloadables (PDFs, sample files)
- Hand-written
robots.txtorsitemap.xml(though the latter is usually better as a page template) - Anything else that ships as-is
Asset URLs and Caching
The deployed asset URLs are stable — /css/styles.css stays /css/styles.css across builds. The build pipeline does not append content hashes by default, so CDN caching is up to the deployment target.
Most deployment targets (Netlify, Vercel, surge.sh) handle cache headers automatically based on file type. For more aggressive cache control, configure the deploy target's cache settings rather than touching the file output.
Common Patterns
Layout includes site-wide assets. The layout is the right place for stylesheets and scripts that every page needs:
<% link('/css/styles.css') %>
<% script('/js/app.js') %>
Pages opt into page-specific assets. A page that needs a special library or stylesheet declares it in the page template:
<% link('/css/blog.css') %>
<% script('/js/comments.js') %>
Blocks declare their own dependencies. A block that needs a syntax highlighter or a charting library declares it where the block is defined:
<!-- blocks/code-sample.ejs -->
<% link('/css/syntax.css') %>
<% script('/js/highlight.js', 'defer') %>
<pre class="code"><code><%= item.source %></code></pre>
Even if the page contains five code-sample blocks, the link and script are emitted once per page — the injection helpers deduplicate automatically.
What's Next
- Tailwind and Styling — Zero-setup Tailwind support and customization.
- Layout — Where site-wide assets are usually declared.
- Online Coding Environment — Adding and editing assets in the browser editor.
- Template Context and Data Access — Full reference for
link(),script(), and other head-injection helpers.