Theme Development

2026-06-20

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:

  • name Template name
  • version Version number
  • author Author
  • repository Code repository
  • demo Demo URL
  • default_template Theme default template name (can be overridden by everkm.yaml#default_template)
  • config Theme-level configuration, read via __config in templates, merged with project everkm.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):

FieldTypeDescription
request_idstringUnique ID for this request, must be passed when calling everkm.* APIs
postPostItem | nullArticle metadata when an article is matched
breadcrumbsBreadcrumbResolved[]Breadcrumbs
configRecord<string, any>Merged site configuration
qsRecord<string, any>Merged query result for the current directory
tpl_pathstringCurrent template path in use
page_pathstringCurrent page path
langstringCurrent language code
hoststring | nullHostname
env_is_previewbooleanWhether 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.

MethodDescription
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).