Slick is a small, simple, and ultrafast web framework built on Web Standards for Deno.
- Fast and lightweight - Built for performance with minimal overhead
- Server-side rendering - Built-in SSR with Preact for optimal SEO
- Automatic asset optimization - CSS, JS, and TS files are minified and transpiled automatically
- SPA capabilities - Seamless client-side navigation with
@webtools/slick-client - Type-safe - Full TypeScript support with type checking
- Static file serving - Efficient static asset delivery
- Easy configuration - Simple setup with sensible defaults
- Web Standards - Built on modern web standards and Deno
Install Slick Server using Deno's package manager:
deno add jsr:@webtools/slick-serverCreate or update your deno.json file with the required configuration:
{
"imports": {
"@webtools/slick-server": "jsr:@webtools/slick-server@^0.4.0"
},
"compilerOptions": {
"jsxImportSource": "preact",
"jsx": "react-jsx"
}
}Important: The compilerOptions are required for JSX to work with Preact. Without them, you'll get compilation
errors when using JSX syntax in your pages and templates.
If you're using SPA mode (client: true), also install the client package:
deno add jsr:@webtools/slick-clientAnd add it to your deno.json:
{
"imports": {
"@webtools/slick-server": "jsr:@webtools/slick-server@^0.4.0",
"@webtools/slick-client": "jsr:@webtools/slick-client@^0.2.0"
},
"compilerOptions": {
"jsxImportSource": "preact",
"jsx": "react-jsx"
}
}my-slick-app/
βββ pages/
β βββ index.tsx
βββ templates/
β βββ app.tsx
βββ static/
β βββ styles/
β βββ scripts/
βββ server.ts
import { Slick } from "@webtools/slick-server";
const slick = new Slick(import.meta.dirname!, {
env: {
API_URL: Deno.env.get("API_URL") || "http://localhost:3000",
},
port: 5000,
lang: "en",
r404: "/",
client: true, // Enable SPA mode
});
await slick.start();import * as Slick from "@webtools/slick-server";
export default {
name: "app",
favicon: "/favicon.ico",
styles: [
"/styles/reset.css",
"/styles/app.css",
],
scripts: [
"/scripts/app.ts",
],
head: (
<>
<meta name="description" content="My Slick App" />
</>
),
body: (
<div id="app">
{/* Page content will be injected here */}
</div>
),
onrequest: null,
} satisfies Slick.Template;import * as Slick from "@webtools/slick-server";
export default {
url: "/",
template: "app",
title: "Home - My App",
styles: [
"/styles/pages/index.css",
],
scripts: [
"/scripts/pages/index.ts",
],
head: (
<>
<meta property="og:title" content="Home" />
</>
),
body: (
<>
<h1>Welcome to Slick Server!</h1>
<p>This is your home page.</p>
</>
),
onpost: null,
onrequest: null,
} satisfies Slick.Page;deno run --allow-net --allow-read server.tsSlick Server requires a specific directory structure:
project-root/
βββ pages/ # Page definitions (required)
β βββ index.tsx
β βββ about.tsx
βββ templates/ # Template definitions (required)
β βββ app.tsx
β βββ admin.tsx
βββ static/ # Static assets (required)
βββ styles/
β βββ reset.css
β βββ app.css
βββ scripts/
β βββ app.ts
βββ assets/
βββ favicon.ico
pages/- Contains page components. Each file must export a defaultPageobject.templates/- Contains template structures. Each file must export a defaultTemplateobject.static/- All files are served as static assets. CSS, JS, and TS files are automatically minified and transpiled.
The Slick constructor accepts a configuration object:
interface Config {
env: Record<string, string>; // Environment variables for static files
port: number; // Server port (default: 5000)
lang: string; // HTML lang attribute (default: "en")
r404: string; // 404 redirect URL (default: "/")
client: boolean; // Enable SPA mode (default: false)
}| Option | Type | Default | Description |
|---|---|---|---|
env |
Record<string, string> |
{} |
Environment variables available in static files via esbuild.define |
port |
number |
5000 |
Port number for the HTTP server |
lang |
string |
"en" |
Language code for the HTML lang attribute |
r404 |
string |
"/" |
URL to redirect to when a page is not found |
client |
boolean |
false |
Enable SPA mode with @webtools/slick-client |
A Page object defines a route and its content:
interface Page {
url: string; // Route URL (must start with /)
template: string; // Template name to use
title: string; // Page title
styles: string[]; // CSS file paths
scripts: string[]; // JS/TS file paths
head: Render | null; // Additional head content
body: Render | null; // Page body content
onpost: RequestListener | null; // POST request handler
onrequest: RequestListener | null; // Request middleware (use res.redirect() for redirects)
}import * as Slick from "@webtools/slick-server";
import type { HttpRequest, HttpResponse } from "@webtools/expressapi";
export default {
url: "/about",
template: "app",
title: "About Us",
styles: ["/styles/pages/about.css"],
scripts: ["/scripts/pages/about.ts"],
head: <meta name="description" content="Learn more about us" />,
body: (
<>
<h1>About Us</h1>
<p>We are a great company!</p>
</>
),
onpost: async (req: HttpRequest, res: HttpResponse) => {
// Handle POST requests
return res.json({ success: true });
},
onrequest: async (req: HttpRequest, res: HttpResponse) => {
// Middleware - can redirect or modify request
if (!req.headers.has("authorization")) {
return res.redirect("/login");
}
},
} satisfies Slick.Page;The head and body properties accept a Render type:
type Render =
| ((req: HttpRequest) => Promise<preact.VNode> | preact.VNode)
| preact.VNode;You can use:
- Static VNode:
<div>Hello</div> - Function:
(req) => <div>Hello {req.url}</div> - Async Function:
async (req) => { const data = await fetchData(); return <div>{data}</div>; }
A Template object defines the page structure:
interface Template {
name: string; // Unique template name
favicon: string; // Favicon path
styles: string[]; // Global CSS files
scripts: string[]; // Global JS/TS files
head: Render | null; // Global head content
body: Render | null; // Template body (must contain element with id="app")
onrequest: RequestListener | null; // Global request middleware (use res.redirect() for redirects)
}import * as Slick from "@webtools/slick-server";
export default {
name: "app",
favicon: "/assets/favicon.ico",
styles: [
"/styles/reset.css",
"/styles/global.css",
],
scripts: [
"/scripts/global.ts",
],
head: (
<>
<meta name="theme-color" content="#000000" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
</>
),
body: (
<>
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<main id="app">
{/* Page content injected here */}
</main>
<footer>
<p>© 2024 My App</p>
</footer>
</>
),
onrequest: null,
} satisfies Slick.Template;Important: The template's body must contain an element with id="app" where page content will be injected.
All files in the static/ directory are served directly. Special handling applies to:
- Automatically minified
- Served with
Content-Type: text/css
- Automatically transpiled (TS β JS)
- Automatically minified
- Served as ES modules
- Environment variables from
config.envare available viaesbuild.define
- Served as-is (images, fonts, etc.)
static/
βββ styles/
β βββ reset.css
β βββ app.css
β βββ pages/
β βββ index.css
βββ scripts/
β βββ app.ts
β βββ pages/
β βββ index.ts
βββ assets/
βββ favicon.ico
βββ logo.png
Environment variables defined in config.env are available in your static TypeScript/JavaScript files:
// static/scripts/app.ts
console.log(API_URL); // Available if defined in config.envWhen client: true is enabled, Slick Server integrates with @webtools/slick-client for SPA functionality:
- Client-side navigation without full page reloads
- Automatic template and page updates
- Optimized asset loading
- Enable client mode in your server config:
const slick = new Slick(import.meta.dirname!, {
client: true,
// ... other config
});- Install the client package in your static scripts:
// static/scripts/app.ts
import { Slick } from "@webtools/slick-client";
// Client is automatically initialized by the server
// You can add custom navigation handlers here- The server injects
@webtools/slick-clientautomatically - Pages are loaded via AJAX when navigating
- Only changed parts (template or page) are updated
- Full HTML is served on initial load for SEO
new Slick(workspace: string, config: Partial<Config>)workspace: Path to your project root (useimport.meta.dirname!)config: Partial configuration object
Starts the server. This method:
- Validates the workspace structure
- Loads all pages and templates
- Registers routes
- Starts the HTTP server
await slick.start();interface Page {
readonly url: string;
readonly template: string;
readonly title: string;
readonly styles: string[];
readonly scripts: string[];
readonly head: Render | null;
readonly body: Render | null;
readonly onpost: RequestListener | null;
readonly onrequest: RequestListener | null;
}interface Template {
readonly name: string;
readonly favicon: string;
readonly styles: string[];
readonly scripts: string[];
readonly head: Render | null;
readonly body: Render | null;
readonly onrequest: RequestListener | null;
}type Render =
| ((req: HttpRequest) => Promise<preact.VNode> | preact.VNode)
| preact.VNode;export default {
url: "/products/:id",
template: "app",
title: "Product",
body: async (req: HttpRequest) => {
const productId = req.url.split("/").pop();
const product = await fetchProduct(productId);
return (
<>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
</>
);
},
// ... other properties
} satisfies Slick.Page;import type { HttpRequest, HttpResponse } from "@webtools/expressapi";
export default {
url: "/dashboard",
template: "app",
title: "Dashboard",
onrequest: async (req: HttpRequest, res: HttpResponse) => {
const token = req.headers.get("authorization");
if (!token || !await validateToken(token)) {
return res.redirect("/login"); // Redirect to login
}
},
// ... other properties
} satisfies Slick.Page;import type { HttpRequest, HttpResponse } from "@webtools/expressapi";
export default {
url: "/api/contact",
template: "app",
title: "Contact",
onpost: async (req: HttpRequest, res: HttpResponse) => {
const { name, email, message } = req.body;
// Process the form submission
await sendEmail({ name, email, message });
return res.json({ success: true, message: "Message sent!" });
},
// ... other properties
} satisfies Slick.Page;Distributed under the MIT License. See LICENSE for more information.