Edit on GitHub

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 .jslop file here becomes a route. See Routing.
  • src/components/ — a convention for shared .jslop components. 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 via css: "/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

Use pnpm, not npm or yarn. The JSlop workspace is pnpm-only.

[!NOTE]
@jslop/server and @jslop/router are 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

Last updated: May 15, 2026