Project structure
A JSlop app is a normal Vite project with a @jslop/vite plugin. Here’s what a minimal app looks like.
File layout
my-app/
├── package.json
├── vite.config.mjs
├── src/
│ ├── routes/
│ │ ├── index.jslop → /
│ │ ├── about.jslop → /about
│ │ ├── _layout.jslop (optional)
│ │ ├── _404.jslop (optional)
│ │ └── posts/
│ │ └── [slug].jslop → /posts/:slug
│ ├── components/ (convention, not required)
│ │ └── Button.jslop
│ └── app.css (optional, for Tailwind / global CSS)
└── serve.mjs (production only)
What each piece is for:
src/routes/— the route tree. Every.jslopfile here becomes a route. See Routing.src/components/— a convention for shared.jslopcomponents. JSlop doesn’t enforce a location; import them from anywhere.src/app.css— your global stylesheet, if you have one. Tell the Vite plugin about it viacss: "/src/app.css".serve.mjs— a small Node entrypoint that serves the production build with@jslop/node-adapter. See Building & deploying.
package.json
The minimum set of dependencies:
{
"name": "my-app",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build && vite build --ssr",
"serve": "node serve.mjs"
},
"dependencies": {
"@jslop/client": "workspace:*",
"@jslop/node-adapter": "workspace:*",
"@jslop/router": "workspace:*",
"@jslop/runtime": "workspace:*",
"@jslop/server": "workspace:*"
},
"devDependencies": {
"@jslop/vite": "workspace:*",
"vite": "^7.0.0"
}
}
Important
[!NOTE]
@jslop/serverand@jslop/routerare listed as runtime dependencies, not dev dependencies. That’s deliberate — the production SSR entry bundles them, and pnpm’s strict resolver refuses to find them if they’re not declared at the project level.
vite.config.mjs
The smallest possible config:
import { defineConfig } from "vite";
import jslop from "@jslop/vite";
export default defineConfig({
plugins: [jslop()],
});
Common options:
jslop({
routesDir: "src/routes", // default: "src/routes"
tailwind: true, // auto-load @tailwindcss/vite
css: "/src/app.css", // global CSS to inject
title: (url) => `Site — ${url}`, // <title> per route
})
See Routing for routesDir, Styling for tailwind and css.
A route file
// src/routes/index.jslop
component Home {
state n = 0
function inc() { n++ }
view {
<main>
<h1>hello</h1>
<button onclick={inc}>clicked {n} times</button>
</main>
}
}
A route file is just a .jslop file that exports a component. The first component declared is the route’s page. You can declare helpers in the same file:
// src/routes/index.jslop
component Home {
view {
<main>
<Greeting name="world" />
<Greeting name="jslop" />
</main>
}
}
component Greeting {
prop name = "?"
view { <p>hello, {name}</p> }
}
The router only routes to the first component in a route file; the rest are local helpers.
A layout file
A _layout.jslop wraps every route in the same folder (and below). Use <children/> to mark where the page should render:
// src/routes/_layout.jslop
component Layout {
view {
<div class="app">
<header><nav>...</nav></header>
<children/>
<footer>© 2026</footer>
</div>
}
}
A 404 file
_404.jslop is the page that renders when no route matches:
// src/routes/_404.jslop
component NotFound {
view {
<main>
<h1>404</h1>
<p>no route matches.</p>
</main>
}
}
It runs through the layout chain like any other page.
Where to put shared components
Anywhere. The convention used in examples/counter is src/components/, but you’re free to organize however you like. Import them from routes:
import { Button, Card } from "../components/widgets.jslop"
import Header from "../components/Header.jslop"
A .jslop file may declare any number of components. The first is the default export; the rest are named exports. See Components.
See also
- Getting started — bootstrap a fresh project.
- Routing — file-system route conventions.
- Building & deploying — production build and
serve.mjs.