@jasonshimmy/vite-plugin-cer-app
| Install | |
|---|---|
npm install @jasonshimmy/vite-plugin-cer-app |
|
| Latest Version: | 0.29.0 |
| License: | MIT |
| Last Updated: | Oct 7, 2026 |
| Links: | GitHub · npm |
@jasonshimmy/vite-plugin-cer-app
A Nuxt/Next.js-style meta-framework built on top of @jasonshimmy/custom-elements-runtime. Turns any Vite project into a full-stack application with file-based routing, server-side rendering, static site generation, server API routes, and more — all through native Web Components.
Features
- File-based routing —
app/pages/directory maps directly to routes - Layouts —
app/layouts/with<slot>composition - Three rendering modes — SPA, SSR (streaming), and SSG
- Server API routes —
server/api/with per-method handlers (GET,POST, …) - Auto-imports — runtime APIs (
component,html,ref, …) injected automatically in page files - Data loading —
loaderexport per page; serialized server→client viawindow.__CER_DATA__ useHead()— document head management (title, meta, OG tags) with SSR injection- App plugins — ordered plugin loading with DI via
provide/inject - Route middleware — global and per-page guards
- Server middleware — CORS, auth, and other HTTP-level middleware
- JIT CSS — Tailwind-compatible, build-time via the runtime's
cerPlugin - HMR — virtual module invalidation when pages/components are added or removed
Installation
npm install -D @jasonshimmy/vite-plugin-cer-app
npm install @jasonshimmy/custom-elements-runtime
Add the plugin to vite.config.ts:
// vite.config.ts
import { defineConfig } from 'vite'
import { cerApp } from '@jasonshimmy/vite-plugin-cer-app'
export default defineConfig({
plugins: [cerApp()],
})
Or use a cer.config.ts alongside vite.config.ts (the CLI reads this automatically):
// cer.config.ts
import { defineConfig } from '@jasonshimmy/vite-plugin-cer-app'
export default defineConfig({
mode: 'spa', // 'spa' | 'ssr' | 'ssg'
})
Quickstart with the CLI
The fastest path is scaffolding a new project:
npx --package @jasonshimmy/vite-plugin-cer-app create-cer-app my-app
# → choose spa / ssr / ssg
cd my-app
npm install
npm run dev
Note: The
--packageflag is required becausecreate-cer-appis bundled inside@jasonshimmy/vite-plugin-cer-apprather than published as a standalone package.
Or install the CLI globally to skip the flag entirely:
npm install -g @jasonshimmy/vite-plugin-cer-app
create-cer-app my-app
cer-app dev
Project Structure
my-app/
├── app/ # All client-side app code
│ ├── pages/
│ │ ├── index.ts # → route /
│ │ ├── about.ts # → route /about
│ │ ├── blog/
│ │ │ ├── index.ts # → route /blog
│ │ │ └── [slug].ts # → route /blog/:slug
│ │ └── [...all].ts # → catch-all /*
│ ├── layouts/
│ │ └── default.ts # Default layout wrapper
│ ├── components/ # Auto-registered custom elements
│ ├── composables/ # Auto-imported composables
│ ├── plugins/ # App plugins (01.store.ts → loaded first)
│ └── middleware/ # Global route middleware
├── server/
│ ├── api/
│ │ ├── users/
│ │ │ ├── index.ts # GET/POST /api/users
│ │ │ └── [id].ts # GET/PUT/DELETE /api/users/:id
│ │ └── health.ts # GET /api/health
│ └── middleware/ # Server-only HTTP middleware
├── public/ # Copied as-is to dist/
├── index.html # HTML entry
└── cer.config.ts # Framework config
.cer/is auto-generated on every dev/build and gitignored. The framework bootstrap (app.ts) lives there and is never user-owned — plugin updates propagate automatically.
Pages
Every file in app/pages/ defines a custom element and optionally exports page metadata and a data loader:
// app/pages/blog/[slug].ts
// component, html, useProps are auto-imported — no import statement needed
component('page-blog-slug', () => {
const props = useProps({ slug: '' })
return html`
<div class="prose">
<h1>${props.slug}</h1>
</div>
`
})
// Optional: page metadata
export const meta = {
layout: 'default',
middleware: ['auth'],
hydrate: 'load',
}
// Optional: server-side data loader
export const loader = async ({ params }) => {
const post = await fetch(`/api/posts/${params.slug}`).then(r => r.json())
return { post }
}
File → Route mapping
| File | Route |
|---|---|
app/pages/index.ts |
/ |
app/pages/about.ts |
/about |
app/pages/blog/index.ts |
/blog |
app/pages/blog/[slug].ts |
/blog/:slug |
app/pages/[...all].ts |
/* |
app/pages/(auth)/login.ts |
/login (group prefix stripped) |
Layouts
// app/layouts/default.ts
component('layout-default', () => {
return html`
<header><nav>...</nav></header>
<main><slot></slot></main>
<footer>...</footer>
`
})
The framework wraps each route's content inside the layout declared by meta.layout. Defaults to 'default' if the file exists.
Server API Routes
// server/api/users/[id].ts
import type { ApiHandler } from '@jasonshimmy/vite-plugin-cer-app/types'
export const GET: ApiHandler = async (req, res) => {
res.json({ id: req.params.id })
}
export const DELETE: ApiHandler = async (req, res) => {
res.status(204).end()
}
useHead()
import { useHead } from '@jasonshimmy/vite-plugin-cer-app/composables'
component('page-about', () => {
useHead({
title: 'About Us',
meta: [
{ name: 'description', content: 'Learn more about us.' },
{ property: 'og:title', content: 'About Us' },
],
})
return html`<h1>About</h1>`
})
Content Layer
Drop Markdown and JSON files into content/ and query them with queryContent().
Numeric ordering prefixes are supported on both directories and files. A leading NN. is stripped from the public content path, which lets you keep source-tree ordering without leaking the prefix into URLs:
content/
01.docs/
01.getting-started.md -> /docs/getting-started
02.routing.md -> /docs/routing
02.blog/
01.index.md -> /blog
02.2026-04-01-hello.md -> /blog/hello
Date-prefixed filenames still work the same way after the numeric prefix is removed.
For content-driven routing, use app/pages/[...all].ts to resolve valid nested URLs at runtime. Unlike app/pages/404.ts, a catch-all page is not treated as a 404 automatically. Existing content routes stay HTTP 200, and in SSG with ssg.routes: 'auto' a catch-all page that uses queryContent() can auto-generate concrete output paths from the content store.
See docs/content.md for the full content-layer API and examples.
Documentation
| Guide | Description |
|---|---|
| Getting Started | Installation, scaffolding, first app |
| Configuration | All cer.config.ts options |
| Routing | File-based routing, dynamic segments, route groups |
| Layouts | Layout system and <slot> composition |
| Components | Auto-registered custom elements |
| Composables | Auto-imported composables |
| Content Layer | File-based Markdown/JSON content with queryContent() and useContentSearch() |
| Plugins | App plugin system and DI |
| Middleware | Route guards and server middleware |
| Server API Routes | HTTP handlers in server/api/ |
| Data Loading | Page loaders and SSR data hydration |
| Head Management | useHead() reference |
| Rendering Modes | SPA, SSR, and SSG in detail |
| CLI Reference | cer-app and create-cer-app commands |
| Manual Testing Guide | How to test every feature end-to-end |
Runtime release maintenance
After publishing a new @jasonshimmy/custom-elements-runtime release, synchronize the plugin with the npm latest tag:
npm run update:runtime
The command updates the plugin's peer and development dependency ranges, the installed runtime record in package-lock.json, and every package.json.tpl used by create-cer-app. Template manifests are discovered recursively, so newly added SPA, SSR, SSG, or future build-mode templates are included automatically. The update aborts before writing if any required reference is missing, registry metadata is incomplete, or the published runtime declares dependencies. Run npm install or npm ci afterward when the local node_modules tree also needs to be refreshed; CI already does this from the updated lockfile.
For a read-only CI/release check, use npm run check:runtime. It exits non-zero and lists stale files when an update is required. If npm's latest tag has not propagated yet, a known published version can be selected explicitly with npm run update:runtime -- --version 3.9.2.
License
MIT
See production content, islands and quality checks for complete Markdown SSR, selective content imports, Vite preview, static CSS and release gates.
Related Packages
Lit web components (tables, forms, inputs, dialogs, charts) for Vue and Nuxt, wi...
Framework-agnostic Web Components for skeehn — custom elements that work in Vue,...
Version History
| Version | Released | License |
|---|---|---|
| 0.29.0 | MIT | |
| 0.28.3 | MIT | |
| 0.28.2 | MIT | |
| 0.28.1 | MIT | |
| 0.28.0 | MIT | |
| 0.27.3 | MIT | |
| 0.27.2 | MIT | |
| 0.27.1 | MIT | |
| 0.27.0 | MIT | |
| 0.26.0 | MIT | |
| 0.25.0 | MIT | |
| 0.24.0 | MIT | |
| 0.23.2 | MIT | |
| 0.23.1 | MIT | |
| 0.23.0 | MIT | |
| 0.22.0 | MIT | |
| 0.21.4 | MIT | |
| 0.21.3 | MIT | |
| 0.21.2 | MIT | |
| 0.21.1 | MIT | |
| 0.21.0 | MIT | |
| 0.20.5 | MIT | |
| 0.20.4 | MIT | |
| 0.20.3 | MIT | |
| 0.20.2 | MIT | |
| 0.20.1 | MIT | |
| 0.20.0 | MIT | |
| 0.19.3 | MIT | |
| 0.19.2 | MIT | |
| 0.19.1 | MIT |
Showing the latest 30 of 67. See every release on npm