This document is for theme developers. Site operators can find theme installation and usage instructions in Quick Start; for Tera template syntax see Introduction, and for built-in functions see Built-in Functions.
Quick Start
Official reference project: theme-youlog. The repository keeps theme source code and demo site together for easy local preview. Theme features and config field descriptions can be found in the theme directory's README.
theme-youlog/
├── HOME.md # Demo content (Markdown)
├── __everkm/
│ ├── everkm.yaml # Demo site configuration
│ └── theme/youlog/ # Theme source code (templates, frontend, build scripts)
│ ├── templates/
│ ├── src/
│ └── everkm-theme.yaml
For workspace directory conventions, see Directory Structure.
Install Dependencies
git clone https://github.com/everkm/theme-youlog.git
cd theme-youlog/__everkm
# Install everkm-publish CLI
pnpm install
# Install theme frontend dependencies
cd theme/youlog && pnpm i
Local Preview
Start the preview server from __everkm/:
cd theme-youlog/__everkm
everkm-publish serve \
--work-dir ../ \
--theme youlog \
--theme-dev \
--listen 0.0.0.0:9081
Access http://localhost:9081 in your browser.
--work-dir ../: workspace points to the repository root (demo Markdown and__everkm/everkm.yaml)--theme youlog: uses the theme under__everkm/theme/youlog/--theme-dev: watches for theme source changes and auto-reloads
If the theme uses TSX development (like youlog), open another terminal and run the frontend watch build under theme/youlog/:
cd theme-youlog/__everkm/theme/youlog
pnpm run dev:jsrender # watch browser assets + templates/everkm-render.js
# or pnpm run dev # watch browser-side assets only
Export Static Site
cd theme-youlog/__everkm
everkm-publish serve \
--work-dir ../ \
--theme youlog \
--theme-dev \
--export
Building and Installing Theme Packages
Theme packaging is handled by the frontend build script; everkm-publish handles installing the packaged .zip:
cd theme-youlog/__everkm/theme/youlog
# 1. Build frontend assets and everkm-render.js
pnpm run build:jsrender
# 2. Assemble publishable directory
mkdir -p dist/assets
cp -r templates dist/
cp assets-manifest.json everkm-theme.yaml dist/
cp assets/* dist/assets/
# 3. Package (optional, top-level directory inside zip is youlog/)
mkdir .dist && mv dist .dist/youlog
cd .dist && zip -r ../youlog.zip youlog && cd .. && rm -rf .dist
# 4. Install to site
everkm-publish theme install ../youlog.zip
The theme-youlog repository provides Makefiles under __everkm/ and theme/youlog/ (e.g., make work, make build) as convenient wrappers for the above commands, use as needed.
Theme Metadata everkm-theme.yaml
name: youlog
version: 0.1.0
author: dayu<dayu@dayu.me>
repository: https://github.com/everkm/theme-youlog
demo: https://youlog.theme.everkm.com/
Field descriptions:
nameTemplate nameversionVersion numberauthorAuthorrepositoryCode repositorydemoDemo URLdefault_templateTheme default template name (can be overridden byeverkm.yaml#default_template)configTheme-level configuration, read via__configin templates, merged with projecteverkm.yaml#config(project overrides theme)
Site operators install themes with:
everkm-publish theme install everkm/youlog@0.5.6
everkm-publish theme install /path/to/youlog.zip --force
Remote installation aligns with the ekmp-themes official catalog. The default theme for init is everkm/youlog.
Page Rendering Fallback
During preview and export, if a URL's corresponding Tera template does not exist (e.g., some themes only have a JS version of index.html), the system automatically falls back to the theme's built-in JS rendering path, allowing both the homepage and article pages to display correctly. Theme developers can provide both Tera and JS template sets, or maintain only the JS version.
JS Rendering (JsRender)
Themes can use JavaScript as an alternative to or complement for Tera templates. everkm-publish includes a lightweight JS runtime that loads the theme's everkm-render.js and calls its exported functions to complete page rendering.
Complete TypeScript type definitions are available in the official theme: https://github.com/everkm/theme-youlog/tree/master/__everkm/theme/youlog/src/types.
Interface Structure
templates/everkm-render.js needs to export three functions, implementable with plain JavaScript:
// Health check -- validates script availability at startup
export function ping() {
return "pong";
}
// Page rendering
// name: template name (e.g., "index.html", "book"), determined by folders.template or default_template
// props: page context JSON object (see below)
// returns: complete HTML string (including <!DOCTYPE html>)
export async function renderPage(name, props) {
const requestId = props.request_id;
const doc = everkm.post_detail(requestId, { id: props.post.id });
const css = everkm.assets(requestId, { type: "css", section: "mytheme" });
const js = everkm.assets(requestId, { type: "js", section: "mytheme" });
return `<!DOCTYPE html>
<html>
<head>${css}</head>
<body>
<h1>${doc.title}</h1>
${doc.content}
${js}
</body>
</html>`;
}
// dCard rendering
// name: card name (e.g., "audio", "download")
// props: card parameters + page_context (the full context of the containing page)
export async function renderDcard(name, props) {
if (name === "audio") {
return `<audio src="${props.media}" controls></audio>`;
}
throw new Error(`Unknown dcard: ${name}`);
}
Rendering Flow
Request URL
-> Determine template name (folders.template / default_template / URL path)
-> Tera template exists? -> Tera rendering
-> Does not exist? -> JsRender: renderPage(name, props)
-> Inject static assets
dCard follows the same logic: first try local .dcard.html template, if not found then call renderDcard.
Themes can provide both Tera and JS template sets, with Tera taking priority; or maintain only the JS version.
Page Context
The props parameter of renderPage (corresponds to TypeScript PageContext):
| Field | Type | Description |
|---|---|---|
request_id | string | Unique ID for this request, must be passed when calling everkm.* APIs |
post | PostItem | null | Article metadata when an article is matched |
breadcrumbs | BreadcrumbResolved[] | Breadcrumbs |
config | Record<string, any> | Merged site configuration |
qs | Record<string, any> | Merged query result for the current directory |
tpl_path | string | Current template path in use |
page_path | string | Current page path |
lang | string | Current language code |
host | string | null | Hostname |
env_is_preview | boolean | Whether in preview mode |
For dCard, props includes the card parameters plus the full PageContext of the containing page via page_context.
everkm Global API
The global JS object everkm provides data query capabilities equivalent to Tera built-in functions. Complete type definitions are available at https://github.com/everkm/theme-youlog/blob/master/__everkm/theme/youlog/src/types/everkm.d.ts.
| Method | Description |
|---|---|
everkm.posts(requestId, args?) | Article list |
everkm.post_detail(requestId, args) | Article detail (including Markdown-to-HTML body) |
everkm.post_meta(requestId, args) | Article metadata |
everkm.has_post(requestId, args) | Whether an article exists |
everkm.nav_tree(requestId, args) | Navigation tree |
everkm.nav_path(requestId, args) | Navigation path |
everkm.nav_indicator(requestId, args) | Previous/next navigation |
everkm.config(requestId, args) | Read configuration values |
everkm.assets(requestId, args) | Static asset tags (CSS/JS) |
everkm.base_url(requestId) | Site base URL |
everkm.asset_base_url(requestId) | Asset base URL |
everkm.data(requestId, args) | Data source query |
everkm.markdown_to_html(content) | Markdown to HTML |
everkm.media(requestId, args) | Local media resource URL |
everkm.media_remote(requestId, args) | Remote media URL |
everkm.media_dimension(requestId, args) | Media dimensions (width/height) |
everkm.page_query(requestId, args) | Paginated query |
everkm.env(requestId, args) | Environment variables |
everkm.lang() | Current language code |
Developing with TSX
Concatenating HTML with plain JS is suitable for simple scenarios. For complex themes, developing with TSX (JSX) is recommended -- organize pages with components, and output to everkm-render.js during build.
This is not SSR in the traditional sense (no runtime server); it leverages TSX's component structure to perform a one-time render inside a JS sandbox, outputting static HTML strings.
Using SolidJS as an example -- it is small, has no virtual DOM, and is well-suited for the JS sandbox environment. There's no need to learn state management, routing, or other framework features; you just need to know how to use TSX to write HTML:
Directory structure (referencing the official theme theme-youlog):
theme/
├── src/
│ ├── entries/
│ │ ├── jsrender.ts # JS rendering entry (exports ping / renderPage / renderDcard)
│ │ └── browser.ts # Browser-side entry (interaction logic)
│ ├── pages/
│ │ ├── index.tsx # Page rendering main logic
│ │ └── book.tsx # Documentation page component
│ ├── layout/
│ │ ├── RootLayout.tsx # Root layout
│ │ ├── Sidebar.tsx # Sidebar
│ │ └── ArticleContent.tsx
│ ├── components/ # Shared components
│ ├── dcard/
│ │ └── index.tsx # dCard rendering
│ └── types/
│ ├── context.d.ts # PageContext / PostItem type definitions
│ └── everkm.d.ts # everkm.* API type definitions
├── templates/ # Build output directory (everkm-render.js goes here)
├── build.js # esbuild build script
└── package.json
Entry file (src/entries/jsrender.ts):
import { renderPage } from "../pages";
import { renderDcard } from "../dcard";
function ping() {
return "pong";
}
export { ping, renderPage, renderDcard };
Page rendering (src/pages/index.tsx):
import { renderToStringAsync } from "solid-js/web";
import { RootLayout } from "../layout/RootLayout";
import { BookPage } from "./book";
async function renderPage(compName: string, props: any) {
const html = await renderToStringAsync(() => {
switch (compName) {
case "book":
return (
<RootLayout context={props}>
<BookPage props={props} />
</RootLayout>
);
default:
throw new Error(`Page ${compName} not found`);
}
});
// Inject CSS/JS assets during the JS rendering stage
const requestId = props.request_id;
const css = everkm.assets(requestId, { type: "css", section: "mytheme" }) || "";
const js = everkm.assets(requestId, { type: "js", section: "mytheme" }) || "";
const withCss = html.replace(/<\/head>/i, `${css}</head>`);
const withJs = withCss.replace(/<\/body>/i, `${js}</body>`);
return `<!DOCTYPE html>${withJs}`;
}
export { renderPage };
dCard rendering (src/dcard/index.tsx):
import { renderToStringAsync } from "solid-js/web";
export async function renderDcard(name: string, props: any) {
return await renderToStringAsync(() => {
switch (name) {
case "items":
return <ItemsCard {...props} />;
default:
throw new Error(`Dcard ${name} not found`);
}
});
}
Build
The JS rendering entry and browser-side code typically share the same esbuild build process, distinguished by environment variables:
// package.json
{
"scripts": {
"build": "NODE_ENV=production node build.js",
"build:jsrender": "JSRENDER=true NODE_ENV=production node build.js"
}
}
Build output goes to templates/everkm-render.js. This file is included with templates/ during theme packaging.
Development Debugging
everkm-publish serve \
--work-dir ../ \
--theme youlog \
--theme-dev
# Frontend watch (in the theme/youlog/ directory)
pnpm run dev:jsrender
Relationship with Tera: JsRender is not a subset of Tera but an equal rendering backend. Themes can provide both Tera templates (.html) and JS rendering (everkm-render.js), with Tera taking priority; or maintain only the JS version.
All implementation details can be found in the official theme theme-youlog.
Site Extension Templates
In addition to theme-bundled templates, site-level template overrides can be placed in __everkm/extend/templates/. During preview, changes to templates and assets under extend/ are automatically reloaded. See Quick Start for details.
For theme UI text and language packs, see Internationalization (i18n).