Vanilla JavaScript Integration Guide
Sveltia CMS works with plain HTML, CSS and JavaScript sites, with no framework or build tools required. It’s a popular choice for small business sites, portfolios and event pages that need editable content without the overhead of a framework.
How It Works
Your repository holds the HTML pages, scripts and content files, and the hosting service publishes them as is:
- Editors change content in Sveltia CMS, which commits JSON files to the repository.
- The hosting service publishes the new commit, with no build step.
- When a visitor opens a page, its JavaScript fetches the JSON files and renders the content.
Sveltia CMS itself is a single-page application served from a CDN, so the admin page doesn’t need a build step either.
Examples
See real-world examples of vanilla JavaScript sites in our showcase. Most of the listed sites include links to their source code, so you can explore how they implemented Sveltia CMS.
Setup
Adding the Admin Page
Create admin/index.html and admin/config.yml in the folder your site is published from, usually the repository root, as described in the Getting Started guide. The admin page is then available at /admin/.
Linking to the Admin Page
Link to the admin page with a regular <a> element:
<a href="/admin/">Edit content</a>Configuration
Storing Content as JSON
Store content in JSON files, which browsers can parse without a library. The following example manages the home page in one file and a list of team members in another:
media_folder: images/uploads
public_folder: /images/uploads
collections:
- name: pages
label: Pages
files:
- name: home
label: Home Page
file: data/home.json
fields:
- { name: title, label: Title }
- { name: body, label: Body, widget: markdown }
- name: members
label: Team Members
label_singular: Team Member
file: data/members.json
identifier_field: name
fields:
- { name: id, label: ID }
- { name: name, label: Name }
- { name: role, label: Role, required: false }
- { name: photo, label: Photo, widget: image, required: false }The two collections work differently:
- The
pagescollection is a file collection. Each file holds the content of one page or section, and the format is determined by the.jsonextension. - The
memberscollection is a single-file collection. Editors add, edit, delete and reorder members one by one, and all of them are stored indata/members.jsonas an array of objects.
Avoiding One File per Entry
An entry collection with the folder option stores each entry in a file of its own. Avoid it on a vanilla JavaScript site. A browser can’t list the files in a folder, so your script can’t find the entries without an index.
Use a single-file collection instead, as in the example above. If you need one file per entry, e.g. for long blog posts, you have to generate an index file at deploy time, such as with a small script in a GitHub Actions workflow. At that point, a build step that renders the pages may be simpler. See the Custom Tooling Integration Guide.
Images
The media_folder is where uploaded images are stored in the repository, and the public_folder is the URL path written to the content. Start the public_folder with a slash, so the image paths work on every page, including pages in subfolders.
If your site is served from a subfolder, e.g. a GitHub Pages project site at https://username.github.io/repo/, include the subfolder in the public_folder, such as /repo/images/uploads.
Loading Content
Fetching JSON Files
Fetch the JSON files with the Fetch API. The following script renders the home page title and the list of team members from the example above:
const loadJSON = async (path) => {
const response = await fetch(path, { cache: 'no-cache' });
if (!response.ok) {
throw new Error(`Failed to load ${path}: ${response.status}`);
}
return response.json();
};
const home = await loadJSON('/data/home.json');
document.querySelector('#title').textContent = home.title;
const members = await loadJSON('/data/members.json');
document.querySelector('#members').replaceChildren(
...members.map(({ name, role }) => {
const item = document.createElement('li');
item.textContent = role ? `${name} (${role})` : name;
return item;
}),
);Load the script as a module, which allows top-level await:
<script type="module" src="/js/main.js"></script>The cache: 'no-cache' option makes the browser check for a newer version of the file on every visit. Without it, visitors may see outdated content for a while after an update. GitHub Pages, for example, lets browsers cache files for 10 minutes.
Rendering Text Safely
Set plain text with the textContent property, as in the example above, rather than innerHTML. A value like <b>Jane</b> is then shown as is instead of being interpreted as HTML. This also protects the page from cross-site scripting (XSS) if the content contains a malicious script.
Rendering Markdown
The RichText field, set with widget: markdown in the example above, saves Markdown text. Convert it to HTML with a library like Marked, and sanitize the HTML with DOMPurify before inserting it into the page. Add the imports to the top of the script, and render the body after loading the home page data:
import { marked } from 'https://cdn.jsdelivr.net/npm/marked@18/lib/marked.esm.js';
import DOMPurify from 'https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.es.mjs';
// ...
document.querySelector('#body').innerHTML = DOMPurify.sanitize(marked.parse(home.body));Both libraries are loaded from a CDN as JavaScript modules, so no installation is needed.
Publishing Changes
Since there’s no build step, the hosting service only has to publish the files in the repository. GitHub Pages with the “Deploy from a branch” option, as well as Netlify and Cloudflare Pages without a build command, publish every new commit automatically. Content changes made in the CMS usually go live within a minute or two.
Limitations
Content rendered by JavaScript isn’t part of the HTML that the server sends. Google can render JavaScript, but some search engines and most social media link previews read only the HTML, so they don’t see the content.
If search engine optimization matters for your site, render the pages at build time with a static site generator or your own build script. See the Framework Guides and the Custom Tooling Integration Guide.
Support for Vanilla JavaScript
We have implemented specific features to enhance the integration of Sveltia CMS with vanilla JavaScript sites:
- Single-file collections: Store all the entries of an entry collection in a single JSON file as an array of objects, which your script can fetch with one request. Editors still add, edit, delete and reorder the entries one by one.
- Top-level List fields: Edit a JSON file whose root is an array, such as a navigation menu, as a single entry of a file collection, using the
rootoption.