@jasonshimmy/vite-plugin-cer-app
v0.15.0
Published
Nuxt-style meta-framework for @jasonshimmy/custom-elements-runtime
Maintainers
Readme
@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-runtimeAdd 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 devNote: 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 devProject 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>`
})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 |
| 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 |
License
MIT
