Production-ready SolidJS boilerplate with Domain Driven Design, Vike SSR/CSR, Tailwind CSS, and TanStack Query.
| Layer | Technology |
|---|---|
| UI Framework | SolidJS 1.9 |
| SSR / Router | Vike 0.4 |
| Styling | Tailwind CSS 4 |
| Data Fetching | TanStack Solid Query + vike-solid-query |
| Testing | Vitest |
| CI | GitHub Actions + Lighthouse CI |
| Runtime | Bun |
# Install
bun install
# Dev server (http://localhost:3000)
bun run dev
# Production build + preview
bun run build && bun run preview
# Tests
bun run test
# Type check
bun run typechecksrc/
├── domain/ # Core business logic (no dependencies)
│ └── todo/
│ ├── todo.entity.ts # Entity + factory function
│ └── todo.repository.ts # Repository interface (port)
│
├── usecase/ # Application logic (depends on domain only)
│ └── todo/
│ ├── create-todo.usecase.ts
│ └── list-todos.usecase.ts
│
├── repository/ # Concrete data access (implements domain ports)
│ └── todo/
│ ├── in-memory-todo.repository.ts
│ └── query-keys.ts
│
├── presentation/ # UI layer
│ ├── components/ # Shared components
│ │ └── Link.tsx
│ ├── layouts/ # App layouts
│ │ └── AppLayout.tsx
│ ├── pages/ # Page-level components
│ │ └── todo/
│ │ └── TodoPage.tsx
│ ├── seo/ # SEO helpers
│ │ ├── SeoHead.tsx
│ │ ├── JsonLd.tsx
│ │ ├── generate-sitemap.ts
│ │ ├── types.ts
│ │ └── index.ts
│ └── styles/
│ └── global.css
│
└── pages/ # Vike routing (thin wrappers)
├── +config.ts # Global config (SSR, Query, Tailwind)
├── +Layout.tsx
├── +Head.tsx # Global SEO head tags
├── index/+Page.tsx # /
├── todo/+Page.tsx # /todo
├── demo-ssr/+Page.tsx # /demo-ssr (SSR demo)
├── demo-csr/+Page.tsx # /demo-csr (CSR demo)
├── demo-mixed/+Page.tsx # /demo-mixed (SSR+CSR combined)
└── _error/+Page.tsx # Error page
presentation → usecase → domain ← repository
↑ │
└────────────────────┘
(implements interface)
- Domain has zero imports from other layers
- Use cases depend on domain interfaces only
- Repository implements domain interfaces
- Presentation composes use cases with concrete repositories
| Feature | SSR (default) | CSR (ssr: false) |
Mixed SSR+CSR |
|---|---|---|---|
| HTML | Pre-rendered on server | Empty shell | Pre-rendered + hydrated |
| SEO | Full content in source | noindex recommended |
Full content in source |
| Data | +data.ts (server-only) |
useQuery / signals (browser) |
+data.ts + client signals |
| Use for | Public pages, SEO content | Dashboards, auth-gated | Blog, e-commerce, interactive pages |
// src/pages/my-page/+config.ts
export default { ssr: false };// src/pages/my-page/+data.ts
export async function data() {
return { items: await fetchFromDB() };
}Server provides initial data (SEO), client adds interactivity after hydration:
// +data.ts — runs on server
export async function data() {
return { articles: await fetchArticles() };
}
// +Page.tsx — SSR content + client interactivity
import { createSignal, onMount } from "solid-js";
import { useData } from "vike-solid/useData";
export default function Page() {
const data = useData(); // SSR data (in HTML)
const [count, setCount] = createSignal(0); // CSR state (after hydration)
onMount(() => { /* client-only: localStorage, timers, etc. */ });
return (
<>
{/* SSR: articles visible in page source */}
<For each={data.articles}>{(a) => <div>{a.title}</div>}</For>
{/* CSR: interactive after hydration */}
<button onClick={() => setCount(c => c + 1)}>Count: {count()}</button>
</>
);
}Each page has +config.ts (title, description) and +Head.tsx (canonical, JSON-LD, breadcrumbs):
// src/pages/my-page/+config.ts
export default {
title: "My Page — Site Name",
description: "Description for search engines.",
};// src/pages/my-page/+Head.tsx
import { SeoHead, JsonLd } from "../../presentation/seo";
export function Head() {
return (
<>
<SeoHead canonical="https://example.com/my-page" />
<JsonLd data={{ type: "WebPage", name: "My Page", url: "https://example.com/my-page" }} />
</>
);
}| Component | Purpose |
|---|---|
<SeoHead> |
canonical, robots, og:type, og:locale, twitter:card |
<JsonLd> |
Structured data (WebSite, WebPage, BreadcrumbList) |
generateSitemap() |
XML sitemap string generator |
Vike auto-generates <title>, og:title, meta description, og:description from +config.ts.
Global +Head.tsx includes viewport, charset, theme-color, preconnect, structured data.
vike-solid-query handles SSR hydration automatically. Use useQuery in components:
import { useQuery } from "@tanstack/solid-query";
function MyComponent() {
const query = useQuery(() => ({
queryKey: ["items"],
queryFn: () => fetch("/api/items").then((r) => r.json()),
}));
return <Suspense fallback="Loading...">{/* use query.data */}</Suspense>;
}Latest automated Lighthouse CI result for /.
| Category | Score |
|---|---|
| ⚪ Performance | pending |
| ⚪ Accessibility | pending |
| ⚪ Best Practices | pending |
| ⚪ SEO | pending |
- URL:
/ - Updated:
pending first main push - Commit:
pending - Workflow: pending
Pipeline runs on push/PR to main:
| Job | Stage | What |
|---|---|---|
install |
Install | bun install --frozen-lockfile + cache |
test |
Test | bun run test |
typecheck |
Test | tsc --noEmit |
build |
Build | bun run build |
lighthouse |
Lighthouse | LHCI autorun against preview server |
Performance ≥ 90%
Accessibility ≥ 90%
Best Practices ≥ 90%
SEO ≥ 90%
Reports uploaded as artifacts (30-day retention).
bun run build
npm install -g @lhci/cli
lhci autorun- Domain — Create entity + repository interface in
src/domain/<feature>/ - Use Case — Create use case in
src/usecase/<feature>/(depends on domain only) - Repository — Implement repository in
src/repository/<feature>/ - Presentation — Create page component in
src/presentation/pages/<feature>/ - Route — Add Vike page in
src/pages/<feature>/+Page.tsx - SEO — Add
+config.tsand+Head.tsxwith title, description, JSON-LD - Test — Write tests for entity + use case
| Command | Description |
|---|---|
bun run dev |
Dev server with HMR |
bun run build |
Production build (client + SSR) |
bun run preview |
Build + preview production |
bun run test |
Run Vitest |
bun run typecheck |
TypeScript type check |
MIT