---
title: Site Modules
description: Learn how to create and use site modules in Sapling
publishedAt: "2024-01-01"
---
# Site Modules
Site modules are a powerful feature in Sapling that allows you to package and distribute entire websites as reusable modules. This approach enables a clear separation between the site's structure/functionality and its content, making it easier to create templated solutions that others can easily customize.
Another great use case for site modules is for web design agencies to create templates that clients can easily customize but the agency can maintain the underlying structure and components under one single module and codebase.
## How Site Modules Work
A site module typically contains all the components, layouts, and routing logic of your website, while allowing the end user to simply provide the content. This is particularly useful for creating reusable website templates, documentation sites, a reusable APIs, or any other type of website where you want to maintain a consistent structure but allow for custom content.
## Creating a Module
To create a site module, you'll need to:
1. Build your site structure with Sapling
2. Export the necessary configuration functions
3. Publish your module to a registry (like JSR)
Here's a basic example of how to structure a site module:
```ts
import { Hono } from "@hono/hono"
import { Layout } from "@sapling/sapling";
export const site = new Hono();
// Define the content types for the site module
export interface LandingLayoutContent {
siteTitle: string;
hero: {
title: string;
subtitle: string;
}
}
// Add a configuration option for the content.
// This could also be something like the configuration for a CMS.
// That way the end user only has to provide an API key.
let siteContent: LandingLayoutContent;
export function configureSite(content: LandingLayoutContent) {
siteContent = content;
}
site.get("/", async (c) => {
if (!siteContent) {
throw new Error("Site content not configured. Call configureSite() before starting the server.");
}
return c.html(
await Layout({
unoConfig: config,
head: html`
`,
}));
});
```
## Publishing a Module
Technically, you could publish your site module to any registry that supports ESM modules. However, we recommend using [JSR](https://jsr.io) because it's free, easy, and has a much better developer experience than NPM.
Follow the documentation from [JSR](https://jsr.io/docs/introduction) to publish your site module. However, it should be pretty much as simple as running `deno publish` from the root of your Sapling site.
## Using a Module
And then the end user can import and configure the site module like this:
```ts
import { site, configureSite } from "jsr:@sapling/site-0";
const content = {
siteTitle: "My Site",
hero: {
title: "Hello, world!",
subtitle: "Welcome to my site.",
},
};
// Configure the site with the content
configureSite(content);
Deno.serve(site.fetch);
```
## Caveats
### Static Files
Static files you are serving from a `static` directory on your Sapling site will not be included in the site module. There are three ways to work around this:
1. Host your files elsewhere such as in a CMS, CDN, etc. And then just use the URLs to the files directly in your site module. **(recommended)**
2. Expect your end users to provide their own URLs for static files. This would be if you don't want to incur the cost of hosting the files yourself.
3. Provide the source files to your end users for them to host themselves which is obviously the most cumbersome but still works.
This is a null point if your site module is primarily designed to hook into a specific CMS or other system because the CMS is likely handling all of the file hosting.
### No Prerendering
Since a site module is sort of designed around the paradigm of no build step, it doesn't support prerendering. This goes hand in hand with the above static files caveat. You could still provide the prerendered HTML to your end users if you'd like.
## Conclusion
And yes, that's really all there is to it. Your site module can be as complex or as simple as you'd like. And because of TypeScript, it's fully typed and you'll have autocompletion for the content you provide.
---
title: Why Sapling?
description: Learn why Sapling is the ideal choice for modern web development
publishedAt: "2024-01-01"
---
# Why Sapling?
Sapling was created with a simple goal: to make building modern server-side rendered (SSR) websites as straightforward as possible. While web development has grown increasingly complex, we believe that creating a website shouldn't require learning complex build tools or managing heavy client-side JavaScript.
## Core Principles
### 1. Zero JavaScript by Default
Sapling takes a HTML-first approach. Your pages are server-rendered and work without JavaScript enabled. This means:
- Better performance
- Improved accessibility
- Enhanced SEO
- Lower bandwidth usage
Add JavaScript only when you need it, not because your framework requires it.
### 2. No Build Step Required
Unlike many modern frameworks, Sapling doesn't require a build step in development:
- Start coding immediately
- No bundler configuration
- Native TypeScript support
### 3. Multi-Runtime Support
Sapling is designed to work across different JavaScript runtimes:
- **Deno**: For modern, secure applications
- **Node.js**: For traditional Node.js environments
- **Bun**: For maximum performance
Choose the runtime that best fits your needs without changing your code.
### 4. Built-in "Tailwind"
Sapling uses [UnoCSS](https://github.com/unocss/unocss) for CSS, which means you can use just about any Tailwind classes and they will just work. We even include the Tailwind reset styles by default so it feels like you're using Tailwind out of the box.
#### Why UnoCSS instead of Tailwind?
The reason we don't use Tailwind directly is because Tailwind relies on configuration files and a build step. Whereas, [UnoCSS](https://unocss.dev) can generate the classes at runtime or on a Layout request in our case. There are definitely tradeoffs to both approaches, but we believe UnoCSS is the better, faster choice for Sapling.
### 5. Simple but Powerful
Sapling provides powerful features without complexity:
- Type-safe templating
- Type-safe routing
- Built-in atomic CSS support with UnoCSS
- Markdown processing
- Layout system
## When to Use Sapling
Sapling is ideal for:
- **Content-focused websites**: Blogs, documentation, marketing sites
- **Server-rendered applications**: Where SEO and initial load performance are crucial
- **Multi-page applications**: Traditional website structures with multiple pages
- **API-driven websites**: You want to serve a few webpages from your API instead of setting up an entirely new frontend project
## When to Consider Alternatives
Sapling might not be the best choice for:
- Single-page applications (SPAs) with complex client-side state
- Applications requiring real-time updates
- Projects that need extensive client-side JavaScript
## Performance Benefits
### Server-Side Rendering
- Faster Time to First Byte (TTFB)
- Better Core Web Vitals
- Reduced client-side processing
### Minimal JavaScript
- Smaller bundle sizes
- Less parsing and execution time
- Reduced memory usage
### Efficient Asset Handling
- Built-in static file serving
- Automatic HTTP caching headers
- Optimized asset loading
## Developer Experience
Sapling prioritizes developer experience:
```typescript
// Simple component creation
function Greeting() {
return html`
Hello, World!
`;
}
// Easy routing
site.get("/", async (c) => c.html(await Home()));
// Straightforward layouts
export default async function Layout(props: LayoutProps) {
return await SaplingLayout({
title: "My Site",
children: props.children
});
}
```
## Modern Features
Despite its simplicity, Sapling includes modern features:
- **TypeScript First**: Full type safety out of the box
- **UnoCSS Integration**: Modern atomic CSS
- **Dark Mode Support**: Built-in dark mode utilities
- **API Routes**: Easy backend functionality
- **Markdown Support**: A first party markdown to HTML implementation with syntax highlighting
## Inspiration
See our [acknowledgements](/docs/acknowledgements) to learn more about the projects that inspired Sapling.
## Community and Ecosystem
Sapling benefits from:
- Regular updates and improvements
- Growing ecosystem of plugins and tools
- Comprehensive documentation
## Getting Started
Ready to try Sapling? Check out our Quick Start guides:
- [Quick Start (Deno)](/docs/quick-start-deno)
- [Quick Start (Node.js)](/docs/quick-start-node)
---
title: Middleware
description: Learn how to use middleware in Sapling
publishedAt: "2024-01-01"
---
# Middleware
Since Sapling is now using Hono for routing please refer to [Hono's Middleware Docs](https://hono.dev/docs/guides/middleware)
---
title: Sapling Islands
description: Learn how to use the sapling-island web component for progressive hydration
publishedAt: "2024-01-01"
---
# Sapling Islands
The `sapling-island` web component is a lightweight solution for implementing islands architecture in your Sapling applications. It allows you to progressively hydrate parts of your page, loading JavaScript and styles only when needed.
## Installation
You can enable Sapling Islands in two ways:
### 1. Layout Configuration (Recommended)
The simplest way is to enable islands in your Layout configuration:
```typescript
export default async function Layout(props: LayoutProps) {
return await SaplingLayout({
enableIslands: true,
// ... other layout options
});
}
```
This will automatically add the necessary script and CSS to your page.
### 2. Manual Installation
Alternatively, you can manually add the required resources. Add the Sapling Islands script to your page via CDN:
```html
```
Add the following CSS to ensure proper rendering of the content inside the islands:
```css
sapling-island {
display: contents;
}
```
There may be some cases where you don't want `display: contents`; however, in most cases it's a good idea to use it.
## Overview
Islands architecture is a pattern where most of your page remains static HTML, with interactive "islands" that get hydrated with JavaScript when needed. This approach can significantly improve initial page load performance by deferring the loading of non-critical JavaScript.
## Basic Usage
Wrap any content that requires JavaScript or additional styles in a `sapling-island` component:
```html
Your interactive content here
```
## Loading Strategies
The component supports several loading strategies through the `loading` attribute:
### Load (Default)
If no loading attribute is specified, or when set to "load", the island loads immediately when the page loads:
```html
```
### Visible
Loads when the component becomes visible in the viewport:
```html
```
### Idle
Loads when the browser is idle:
```html
```
### Media Query
Loads when a media query condition is met:
```html
```
## Hydration State
When an island is hydrated, it receives a `hydrated` attribute that you can use for styling:
```css
sapling-island[hydrated] {
/* Styles for hydrated state */
}
```
## Timeout Option
You can specify a timeout for loading strategies using the `timeout` attribute. The island will hydrate when either the loading strategy condition is met OR the timeout is reached, whichever comes first:
```html
```
In the example above, the island will hydrate either when it becomes visible **OR** after 5 seconds, whichever happens first.
## Events
The component dispatches a `island:hydrated` event when hydration is complete:
```javascript
document.querySelector('sapling-island').addEventListener('island:hydrated', () => {
console.log('Island has been hydrated');
});
```
## Best Practices
1. **Template Content Only**
Place `
...
```
2. **Progressive Enhancement**
Design your islands to enhance existing static content rather than being required for basic functionality:
```html
Static content
Interactive features
```
3. **Performance Considerations**
- Use `visible` for below-the-fold content
- Use `idle` for non-critical enhancements
- Use media queries for device-specific features
- Set appropriate timeouts for critical features
## Example: Interactive Time Display
Here's a complete example of using `sapling-island` for a dynamic time display:
```html
The time updates every second after hydration:
Current time:
```
## Browser Support
The `sapling-island` component uses standard web APIs and includes fallbacks for broader browser support:
- Uses `IntersectionObserver` for visibility detection
- Falls back to `setTimeout` when `requestIdleCallback` is not available
- Supports all modern browsers that implement Custom Elements v1
For more examples and use cases, check out our [Examples](/docs/examples) section.
---
title: JSX Templating
description: Learn how to use Sapling's JSX templating for building UI
publishedAt: "2024-01-01"
---
# JSX Templating
Sapling utilizes JSX for defining UI components and rendering HTML content. This approach allows you to write HTML-like syntax directly within your JavaScript or TypeScript code, providing a powerful and familiar way to build interfaces. Sapling leverages Hono's JSX implementation (`@hono/hono/jsx`) for this purpose. It is based on Hono's JSX implementation, which you can learn more about [here](https://hono.dev/docs/guides/jsx).
## Basic JSX Syntax
JSX lets you write markup that looks similar to HTML:
```tsx
import { FC } from "@hono/hono/jsx"; // Optional for typing components
// Define a simple functional component
function Greeting({ name }: { name: string }) {
// Return JSX elements
return (
// Tailwind CSS classes are used for styling
Hello, {name}!
);
}
// Usage:
//
```
Key differences from HTML:
- Attribute names use camelCase (e.g., `className` instead of `class`, `onClick` instead of `onclick`).
- JavaScript expressions can be embedded using curly braces `{}`.
- Components are typically defined as functions or classes.
### Dynamic Content Interpolation
Embed JavaScript expressions directly within your JSX using curly braces:
```tsx
function UserProfile({ user }: { user: { name: string; age: number } }) {
const profileUrl = `/users/${user.name.toLowerCase()}`;
return (
);
}
```
### Safety Features
JSX automatically escapes interpolated string values to prevent XSS attacks when rendering them as text content.
```tsx
const userInput = '';
// Renders as plain text:
<script>alert("XSS")</script>
{userInput}
```
**Note:** This automatic escaping *only* applies to content within curly braces `{}` used as element children or attribute values. Setting HTML content directly requires specific methods (see `dangerouslySetInnerHTML`).
## Components
Break down your UI into reusable components. Components are typically functions that accept `props` (properties) as an argument and return JSX.
```tsx
import { FC } from "@hono/hono/jsx"; // Optional but recommended for type safety
interface HeaderProps {
siteTitle: string;
}
const Header: FC = ({ siteTitle }) => {
return
{siteTitle}
;
};
const Page: FC = () => {
return (
Page Content
);
};
```
### Fragments
If you need to return multiple elements from a component without adding an extra wrapper `div`, use Fragments:
```tsx
import { Fragment } from "@hono/hono/jsx";
function UserInfo({ name, email }) {
return (
{name}
{email}
// Short syntax: <>...> also works
// <>
//
{name}
//
{email}
// >
);
}
```
## Rendering Raw HTML
Sometimes you need to render HTML content that comes from an external source (like a CMS or markdown renderer) without escaping. **Use this feature with extreme caution as it can expose your application to XSS attacks if the content is not properly sanitized beforehand.**
JSX provides the `dangerouslySetInnerHTML` prop for this:
```tsx
import { renderMarkdown } from "@sapling/markdown"; // Example markdown renderer
async function Article({ markdownContent }: { markdownContent: string }) {
// Assume renderMarkdown sanitizes the output or the source is trusted
const htmlContent = await renderMarkdown(markdownContent);
return (
);
}
```
**Key Points:**
- The prop name includes "dangerously" to remind you of the risks.
- It accepts an object with a `__html` key, whose value is the raw HTML string.
- **Always sanitize** HTML content before using `dangerouslySetInnerHTML` unless it comes from a completely trusted source.
## Conditional Rendering
Use standard JavaScript conditional logic (like ternary operators or `&&`) within your JSX:
```tsx
function ConditionalContent({ isLoggedIn }: { isLoggedIn: boolean }) {
return (
{isLoggedIn
?
:
}
{/* Render only if logged in */}
{isLoggedIn &&
Welcome back!
}
);
}
```
## List Rendering
Render lists of items by mapping over an array and returning JSX for each item. Remember to include a unique `key` prop for each list item to help efficiently update the DOM.
```tsx
function ItemList({ items }: { items: { id: string; text: string }[] }) {
return (
{items.map(item => (
{item.text}
// Use a stable, unique key
))}
);
}
```
## Best Practices
1. **Component Composition**: Build complex UIs by composing smaller, reusable components.
2. **Props for Data Down**: Pass data down from parent to child components via props.
3. **Type Safety**: Use TypeScript with `FC` or interface definitions for props to catch errors early.
4. **Keys for Lists**: Always provide stable `key` props when rendering lists.
5. **Minimize Raw HTML**: Avoid `dangerouslySetInnerHTML` whenever possible. If used, ensure rigorous sanitization.
6. **Keep Components Focused**: Aim for components that do one thing well.
## Security Considerations
- JSX automatically escapes dynamic text content, preventing many common XSS vulnerabilities.
- **`dangerouslySetInnerHTML` bypasses this escaping**. Only use it with trusted, sanitized HTML.
- Always validate and sanitize any user-generated content before rendering it, especially if using `dangerouslySetInnerHTML`.
- Be cautious when rendering content fetched from external APIs or third-party sources.
---
title: LLMs Reference File
description: Learn about Sapling's LLMs reference file for AI assistants
publishedAt: "2024-01-01"
---
# LLMs Reference File
Sapling provides a comprehensive reference file for AI language models at https://sapling.land/docs/llms.txt. This file contains the entire documentation in a single, easily accessible format that AI assistants can use to better understand and work with Sapling.
## Purpose
The LLMs reference file serves several purposes:
- Provides AI assistants with complete documentation in one file
- Enables better context and understanding of Sapling's features
- Helps AI tools provide more accurate assistance
- Ensures consistent information across different AI platforms
## Accessing the File
The LLMs reference file is available at:
```txt
https://sapling.land/docs/llms.txt
```
Or download it below:
Download llms.txt
## Contents
The file includes all documentation sections:
- Getting Started guides
- Core Concepts
- API References
- Best Practices
- Examples
- FAQs
## Generation
The LLMs reference file is automatically generated from the documentation markdown files. This ensures it stays up-to-date with the latest documentation changes.
## Usage
When working with AI assistants or tools:
1. Point it to the LLMs file url below or upload it to your project for comprehensive documentation
```txt
https://sapling.land/docs/llms.txt
```
2. Use it as a reference for accurate information about Sapling
3. Include it in your project's AI configuration files
## Example
You could add this to Cursor as a doc reference to use whenever you need to reference Sapling's documentation.
---
title: Examples
description: Explore example projects built with Sapling
publishedAt: "2024-01-01"
---
# Examples
Explore our collection of example projects to learn how to build different types of applications with Sapling. All examples are available in our [Examples GitHub Repository](https://github.com/withsapling/examples).
## Basic Examples
- [Basics](https://github.com/withsapling/examples/tree/main/deno/basics) - A basic Sapling application
- [Hello World](https://github.com/withsapling/examples/tree/main/deno/hello-sapling) - A minimal Sapling application
- [Basic Landing Page](https://github.com/withsapling/examples/tree/main/deno/single-file-landing-page) - Simple landing page example
## CMS Examples
- [Sanity](https://github.com/withsapling/examples/tree/main/deno/with-sanity) - [Sanity](https://sanity.io) CMS example
- [Contentful](https://github.com/withsapling/examples/tree/main/deno/with-contentful) - [Contentful](https://www.contentful.com) CMS example
- [Zenblog](https://github.com/withsapling/examples/tree/main/deno/with-zenblog) - [Zenblog](https://zenblog.com) CMS example
... More coming soon (PayloadCMS, Notion, Hygraph, DatoCMS, Storyblok, Prismic, etc.)
## Framework Examples
- [Alpine.js](https://github.com/withsapling/examples/tree/main/deno/with-alpinejs) - Using Sapling with Alpine.js
- [HTMX](https://github.com/withsapling/examples/tree/main/deno/with-htmx) - Using Sapling with HTMX
- [Web Components](https://github.com/withsapling/examples/tree/main/deno/with-web-components) - Using Web Components. We know this is a not a framework (technically) #usetheplatform
- [Petite Vue](https://github.com/withsapling/examples/tree/main/deno/with-petite-vue) - Using Petite Vue
- [React Component](https://github.com/withsapling/examples/tree/main/deno/with-react-component) - Using a React Component bundled with Vite
## Integration Examples
- [Motion](https://github.com/withsapling/examples/tree/main/deno/with-motion) - Animation with Motion
- [Vite + Sapling](https://github.com/withsapling/examples/tree/main/deno/vite-sapling) - Using Sapling with Vite
- [Embedded React App](https://github.com/withsapling/examples/tree/main/deno/with-react-app) - Embedding a client-side React App
- [Gemini Chat](https://github.com/withsapling/examples/tree/main/deno/with-ai-gemini) - Simple AI example to send a message to Gemini
- [OpenAI Chat](https://github.com/withsapling/examples/tree/main/deno/with-ai-openai) - Simple AI example to send a message to ChatGPT
- [Resend Emails](https://github.com/withsapling/examples/tree/main/deno/with-resend) - Sending emails from a form with Resend
- [Iconify Icons](https://github.com/withsapling/examples/tree/main/deno/with-iconify) - Using Iconify Icons
- [Satori Open Graph Images](https://github.com/withsapling/examples/tree/main/deno/satori-og-images) - Generating Open Graph Images Server-Side with Satori
- [Deno KV](https://github.com/withsapling/examples/tree/main/deno/with-deno-kv) - Using Deno KV
## Misc Examples
- [Sitemap](https://github.com/withsapling/examples/tree/main/deno/generate-sitemap) - Generating a Sitemap
## Runtime Examples
- [Deno](https://github.com/withsapling/examples/tree/main/deno) - Deno examples
- [Node](https://github.com/withsapling/examples/tree/main/node) - Node.js examples
- [Cloudflare Workers](https://github.com/withsapling/examples/tree/main/cf-workers) - Cloudflare Workers examples
- [Bun](https://github.com/withsapling/examples/tree/main/bun) - Bun examples
## Deployment Examples
- [Deno Deploy](https://github.com/withsapling/examples/tree/main/deployment/deno-deploy)
- [Cloudflare Workers](https://github.com/withsapling/examples/tree/main/deployment/cf-workers/hello-sapling)
- [Vercel Edge](https://github.com/withsapling/examples/tree/main/deployment/vercel-edge)
- [Google Cloud Run Deno](https://github.com/withsapling/examples/tree/main/deployment/google-cloud-run-deno)
- [Google Cloud Run Bun](https://github.com/withsapling/examples/tree/main/deployment/google-cloud-run-bun)
- [Google Cloud Run Node](https://github.com/withsapling/examples/tree/main/deployment/google-cloud-run-node)
## Running Examples
1. Clone the repository:
```bash
git clone https://github.com/withsapling/examples.git
cd examples
```
2. Navigate to an example:
```bash
cd deno/hello-deno # or any other example
```
3. Start the development server:
```bash
deno task dev # for Deno examples
# or
npm run dev # for Node.js examples
```
Each example includes its own README with specific instructions and explanations.
---
title: FAQ
draft: false
publishedAt: "2024-01-01"
---
# Frequently Asked Questions
## What is Sapling?
Sapling is a lightweight web framework for building server-rendered websites with TypeScript. It's designed to be simple, fast, and easy to learn while providing modern development features.
## Why should I choose Sapling over other frameworks?
Sapling offers:
- Zero client-side JavaScript by default
- Simple, intuitive HTML templating
- First-class TypeScript support
- Fast server-side rendering
- Works with any JS runtime such as Deno, Node.js, Bun, etc.
- No build step required
- Code that embraces web standards rather than fights them
## Can I still use React or Svelte or other frontend frameworks?
Yes, because Sapling is more of a bare bones server side framework that generates HTML you can use any frontend framework you prefer in specific parts of your website/application. We might be biased but we think most sites are better off with plain old HTML, CSS, and JS with a sprinkling of framework specific components.
## Do I need to know TypeScript to use Sapling?
While Sapling is built with TypeScript, you can write your code in JavaScript if you prefer. However, using TypeScript provides better developer experience with type checking and IDE support.
## Do I need to use Deno to use Sapling?
No, Sapling works with all major JavaScript runtime environments including Deno, Node.js, Bun, and CloudFlare Workers. You'll find that we use Deno often in documentation and examples because it's our preferred runtime with built-in TypeScript support which allows us to skip the configuration and build step. However, that doesn't mean Sapling is limited to Deno.
## Do I have to use Sapling for routing?
No, you can use any routing solution you prefer. We aim to be compatible with the most popular server routers out there such as [Hono](https://github.com/withsapling/examples/tree/main/deno/hello-hono), [Express](https://github.com/withsapling/examples/tree/main/node/hello-express), [Oak](https://github.com/withsapling/examples/tree/main/deno/hello-oak), etc. Sapling's routing is designed to be simple, easy to use, and adds features specific to websites, but it may not meet the needs of all applications. In those cases simply use our Layout component inside of your pages to return HTML.
## Why did you build Sapling when Hono already exists?
Hono is a great framework and we love it so much so that it's our preferred framework for making a Sapling project. However, Hono is more of a foundation than a complete SSR solution like we are aiming to offer with built-in Tailwind support, island-architecture, etc. We also want to be clear that Sapling's Layout component is not tied to Hono meaning you could use it inside an application running on Express without any issue.
## Where can I deploy Sapling applications?
Pretty much anywhere!
Sapling applications can be deployed to any platform that supports Deno or Node.js, including:
- [Deno Deploy](https://deno.com/deploy) ([Example](https://github.com/withsapling/examples/tree/main/deployment/deno-deploy))
- [Google Cloud Run](https://cloud.google.com/run) ([Deno Example](https://github.com/withsapling/examples/tree/main/deployment/google-cloud-run-deno), [Bun Example](https://github.com/withsapling/examples/tree/main/deployment/google-cloud-run-bun), [Node Example](https://github.com/withsapling/examples/tree/main/deployment/google-cloud-run-node))
- [Vercel Edge Functions](https://vercel.com/features/edge-functions) ([Example](https://github.com/withsapling/examples/tree/main/deployment/vercel-edge))
- [Cloudflare Workers](https://workers.cloudflare.com/) ([Example](https://github.com/withsapling/examples/tree/main/deployment/cf-workers))
- Netlify Edge Functions
- AWS Lambda
- Render
- Your own server running Deno, Node.js, or Bun
## How do I handle forms and user input?
This would come from your framework such as Hono or Express and simply pass in the data to the the Sapling component/page.
## Is Sapling production-ready?
Yes, Sapling is being used in production by various projects. However, as with any technology choice, make sure to evaluate it against your specific requirements. We are still not at version 1.0 and are actively working on improving the framework.
## Where can I get help?
- Check the documentation
- Open an issue on GitHub
- Follow the examples in our [repository](https://github.com/withsapling/examples)
## How can I contribute to Sapling?
We welcome contributions! You can:
- Submit bug reports and feature requests on GitHub
- Contribute to the documentation
- Submit pull requests
- Share your Sapling projects with the community
---
title: Routing
description: Learn how routing works in Sapling
publishedAt: "2024-01-01"
---
# Routing
Since `0.6.0` Sapling now recommends using Hono for routing. Please refer to their [routing docs](https://hono.dev/docs/api/routing#routing) for more information.
---
title: HTML Templating
description: Learn how to use Sapling's HTML templating methods
publishedAt: "2024-01-01"
---
# HTML Templating
Sapling provides two main methods for rendering HTML content: `html` and `raw`. These are based on the `html` and `raw` methods from [Hono](https://hono.dev) and we aim to be as compatible as possible with Hono's API to allow for easy migration between the two.
## The html Template Literal
The `html` template literal allows you to write HTML with dynamic content interpolation:
```typescript
import { html } from "@hono/hono/html";
function Greeting({ name }: { name: string }) {
return html`
Hello, ${name}!
`;
}
```
### Safety Features
The `html` template automatically escapes interpolated values to prevent XSS attacks:
```typescript
const userInput = '';
html`
${userInput}
`; // Safely escaped
```
### Nested Components
You can nest components and interpolate their results:
```typescript
function Header() {
return html`Site Header`;
}
function Page() {
return html`
${Header()}
Content
`;
}
```
## The raw Method
The `raw` method is used when you need to insert pre-rendered HTML content without escaping:
```typescript
import { html, raw } from "@sapling/sapling";
function Article({ content }: { content: string }) {
return html`
${raw(content)}
`;
}
```
### Use Cases for raw
- Rendering markdown content
- Inserting sanitized HTML from a CMS
- Including pre-rendered component output
```typescript
import { renderMarkdown } from "@sapling/markdown";
async function MarkdownContent({ markdown }: { markdown: string }) {
const rendered = await renderMarkdown(markdown);
return html`
${raw(rendered)}
`;
}
```
## Conditional Rendering
You can use standard JavaScript expressions within templates:
```typescript
function ConditionalContent({ isLoggedIn }: { isLoggedIn: boolean }) {
return html`
${isLoggedIn
? html``
: html``
}
`;
}
```
## List Rendering
Render arrays of content using map:
```typescript
function ItemList({ items }: { items: string[] }) {
return html`
${items.map(item => html`
${item}
`)}
`;
}
```
## Best Practices
1. **Use html by Default**: Always use the `html` template literal unless you specifically need `raw`
2. **Sanitize Raw Content**: When using `raw`, ensure the content is from a trusted source or properly sanitized
3. **Type Safety**: Leverage TypeScript for component props
4. **Keep It Simple**: Prefer small, focused components over complex templates
5. **Performance**: Avoid unnecessary nesting of templates
## Security Considerations
- The `html` template literal automatically escapes content to prevent XSS
- Only use `raw` with trusted content
- Always sanitize user-generated content before rendering
- Be cautious when rendering HTML from external sources
---
title: API Routes
description: Learn how to create and use API routes in Sapling
publishedAt: "2024-01-01"
---
# API Routes
API routes allow you to create server-side endpoints that can handle HTTP requests and return JSON or other data formats. Sapling makes it easy to create API routes alongside your regular page routes.
## Basic API Route
Create an API route by using the `app.get()`, `app.post()`, or other HTTP method handlers:
```typescript
// routes/api/hello.ts
app.get("/api/hello", (c) => {
return c.json({ message: "Hello from Sapling API!" });
});
```
## HTTP Methods
Sapling supports all standard HTTP methods:
```typescript
// GET request
app.get("/api/users", (c) => {
return c.json({ users: ["Alice", "Bob"] });
});
// POST request
app.post("/api/users", async (c) => {
const body = await c.req.json();
// Handle user creation
return c.json({ success: true });
});
// PUT request
app.put("/api/users/:id", async (c) => {
const id = c.req.param("id");
const body = await c.req.json();
// Handle user update
return c.json({ success: true });
});
// DELETE request
app.delete("/api/users/:id", (c) => {
const id = c.req.param("id");
// Handle user deletion
return c.json({ success: true });
});
// PATCH request
app.patch("/api/users/:id", async (c) => {
const id = c.req.param("id");
const body = await c.req.json();
// Handle partial user update
return c.json({ success: true });
});
```
## Request Handling
### Query Parameters
Access query parameters using `c.query()`:
```typescript
app.get("/api/search", (c) => {
const query = c.query().get("q");
const page = parseInt(c.query().get("page") || "1");
return c.json({
query,
page,
results: []
});
});
```
### Route Parameters
Access route parameters using `c.req.param()`:
```typescript
app.get("/api/users/:id/posts/:postId", (c) => {
const userId = c.req.param("id");
const postId = c.req.param("postId");
return c.json({
userId,
postId
});
});
```
### Request Body
Parse JSON request bodies using `c.jsonData()`:
```typescript
app.post("/api/posts", async (c) => {
const body = await c.jsonData<{ title: string; content: string }>();
// Validate the request body
if (!body.title || !body.content) {
return new Response(JSON.stringify({ error: "Missing required fields" }), {
status: 400,
headers: { "Content-Type": "application/json" }
});
}
// Process the post
return c.json({
id: "new-post-id",
...body
});
});
```
### Form Data
Handle form data using `c.formData()`:
```typescript
app.post("/api/upload", async (c) => {
const form = await c.req.formData();
const file = form.get("file");
// Process the file
return c.json({ success: true });
});
```
## Response Types
### JSON Responses
Return JSON responses using `c.json()`:
```typescript
app.get("/api/data", (c) => {
return c.json({
success: true,
data: {
message: "Hello World"
}
});
});
```
### HTML Responses
Return HTML responses using `c.html()`:
```typescript
app.get("/page", (c) => {
return c.html("
Welcome to my page
");
});
```
### Text Responses
Return plain text responses using `c.text()`:
```typescript
app.get("/plain", (c) => {
return c.text("Hello World");
});
```
### Custom Responses
For custom status codes or headers, use the Response object directly:
```typescript
app.post("/api/items", async (c) => {
// Created successfully
return new Response(JSON.stringify({ id: "new-item" }), {
status: 201,
headers: { "Content-Type": "application/json" }
});
});
app.get("/api/protected", (c) => {
// Unauthorized
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { "Content-Type": "application/json" }
});
});
```
### Redirects
Perform redirects using `c.redirect()`:
```typescript
app.get("/old-page", (c) => {
return c.redirect("/new-page");
});
app.get("/permanent-redirect", (c) => {
return c.redirect("/new-location", 301);
});
```
## Error Handling
Implement error handling for your API routes:
```typescript
app.get("/api/protected-resource", async (c) => {
try {
// Check authentication
const user = await authenticateUser(c);
if (!user) {
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { "Content-Type": "application/json" }
});
}
// Process request
const data = await fetchProtectedData();
return c.json(data);
} catch (error) {
console.error("API Error:", error);
return new Response(JSON.stringify({ error: "Internal Server Error" }), {
status: 500,
headers: { "Content-Type": "application/json" }
});
}
});
```
## TypeScript Support
Use TypeScript interfaces for type-safe API handling:
```typescript
interface User {
id: string;
name: string;
email: string;
}
interface CreateUserBody {
name: string;
email: string;
}
app.post("/api/users", async (c) => {
const body = await c.jsonData();
const user: User = {
id: crypto.randomUUID(),
name: body.name,
email: body.email
};
return c.json(user);
});
```
## Best Practices
1. **Versioning**: Consider versioning your API routes:
```typescript
app.get("/api/v1/users", (c) => {
// V1 implementation
});
app.get("/api/v2/users", (c) => {
// V2 implementation
});
```
2. **Validation**: Validate request data before processing:
```typescript
app.post("/api/comments", async (c) => {
const body = await c.jsonData<{ content: string }>();
if (!body.content || body.content.length < 10) {
return new Response(JSON.stringify({
error: "Comment must be at least 10 characters"
}), {
status: 400,
headers: { "Content-Type": "application/json" }
});
}
// Process valid comment
return c.json({ success: true });
});
```
3. **Custom 404 Handler**: Set up a custom 404 handler:
```typescript
app.notFound((c) => {
return c.html(await NotFoundLayout());
});
```
## Testing API Routes
Create tests for your API routes:
```typescript
// tests/api.test.ts
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
Deno.test("GET /api/hello returns correct message", async () => {
const response = await fetch("http://localhost:3000/api/hello");
const data = await response.json();
assertEquals(response.status, 200);
assertEquals(data.message, "Hello from Sapling API!");
});
```
For more examples and use cases, check out our [Examples](/docs/examples) section.
---
title: VS Code Extensions
description: Recommended VS Code extensions for Sapling development
publishedAt: "2024-03-21"
---
# VS Code Extensions
To enhance your development experience with Sapling, we recommend installing the following VS Code extensions:
## Essential Extensions
### 1. UnoCSS
[UnoCSS](https://marketplace.cursorapi.com/items?itemName=antfu.unocss)
This extension provides intelligent autocomplete and hover previews for UnoCSS classes. Since Sapling uses UnoCSS for styling, this extension will greatly improve your development experience by:
- Providing intelligent class name suggestions
- Showing hover previews of the generated CSS
- Supporting custom rules and configurations
- Offering quick completions for utility classes
### 2. Lit Plugin
[Lit Plugin](https://marketplace.cursorapi.com/items?itemName=runem.lit-plugin)
While Sapling doesn't use Lit directly, this extension provides excellent support for HTML template literals, which are a core part of Sapling's templating system. Key features include:
- Syntax highlighting for HTML template literals
- Type checking in template expressions
- Auto-completion for HTML tags and attributes
- Quick info on hover
- Go to definition support
## Installation
The easiest way to install these extensions is to add them to your workspace recommendations. Create or edit `.vscode/extensions.json` in your project root with the following content:
```json
{
"recommendations": [
"antfu.unocss",
"runem.lit-plugin"
]
}
```
VS Code will automatically prompt you to install these recommended extensions when opening the project for the first time.
## Additional Benefits
These extensions work together to provide a seamless development experience:
- **Improved Code Intelligence**: Get better type checking and autocompletion for both your HTML templates and CSS classes
- **Enhanced Productivity**: Save time with intelligent suggestions and quick documentation access
- **Better Error Detection**: Catch potential issues early with built-in type checking and validation
- **Modern Development**: Full support for the modern development features that Sapling provides
---
title: Quick Start (Deno)
description: Get started with Sapling using Deno
publishedAt: "2024-01-01"
---
# Quick Start with Deno
This guide will help you create your first Sapling website using Deno.
## Prerequisites
- [Deno](https://deno.com) installed on your machine
- A code editor (we recommend VS Code with the Deno extension)
## Create a New Project
```bash
deno -A jsr:@sapling/create
```
Select the "Basics" template.
## Start the Development Server
```bash
deno task dev
```
Your site is now running at [http://localhost:8080](http://localhost:8080)!
## Project Structure
```bash
my-website/
├── components/ # Reusable UI components
├── layouts/ # Page layouts
├── pages/ # Your pages/routes
├── static/ # Static assets
├── deno.json # Project configuration
└── index.ts # Entry point
```
## Create Your First Page
1. Create a new file `pages/about.ts`:
```typescript
import Layout from "../layouts/Layout.ts";
import { html } from "@hono/hono/html";
export default async function About() {
return await Layout({
children: html`
`,
});
}
```
2. Add the route in `index.ts` underneath the `Home` route:
```typescript
site.get("/about", async (c) => c.html(await About()));
```
Visit [http://localhost:8080/about](http://localhost:8080/about) to see your new page!
## Next Steps
- Learn about [Routing](/docs/routing)
- Explore [Layouts](/docs/layouts)
- Check out the [Examples](https://github.com/withsapling/examples)
---
title: Cursor Rules
description: A custom .cursorrules.txt file for Cursor's AI
publishedAt: "2025-01-10"
---
# Cursor Rules
Below is a custom .cursorrules file you can add to your Sapling project. Place this text in a file named `.cursorrules` in the root of your project and it will be used by [Cursor](https://cursor.com) whenever you are using AI.
```txt
You are an expert AI assistant and exceptional senior software developer, specializing in TypeScript with deep knowledge of the Sapling framework, and best practices.
Sapling is a framework for building web applications. It is a server-side framework that allows you to build web applications in a way that is similar to how you would build a static site.
Here are some of the best practices for using Sapling:
1. HTML Templating:
- Use html\` \` template literals for safe HTML rendering
- Use raw() for pre-rendered HTML content
- Support for conditional and list rendering
- HTML template literals automatically join arrays of strings, so there is no need to use .join("")
- Default to Tailwind CSS classes for styling unless it makes more sense to be CSS in a style tag.
- If you need to use CSS, place it in the head: property of the Sapling Layout component
- Use the @dark: prefix for dark mode
- ALWAYS be sure to escape any backticks inside of html\`\` template literals
- IMPORTANT: When using html\`\` template literals inside of
\`,
// the bodyClass property is strictly a string for the class attribute of the body tag
bodyClass: "bg-white text-gray-900 @dark:bg-black @dark:text-white transition-colors duration-200",
// the children property is for all of the content to be rendered inside the body tag
children: html\`
\${HelloWorldComponent()}
This was server rendered at \${time}
Learn More
\`,
})
);
});
Deno.serve(site.fetch);
Documentation:
- https://sapling.land/docs/llms.txt
- https://sapling.land/docs
```
---
title: Layouts
description: Learn how to use layouts in Sapling to create consistent page structures
publishedAt: "2024-01-01"
---
# Layouts
Layouts in Sapling provide a way to create consistent page structures and share common elements across multiple pages.
## Basic Layout
A basic layout in Sapling is a TypeScript function that wraps your page content. Here's a simple example:
```typescript
import { Layout as SaplingLayout, type LayoutProps } from "@sapling/sapling";
import { html } from "@hono/hono/html"
export type BaseLayoutProps = LayoutProps & {
title?: string;
description?: string;
};
export default async function Layout(props: BaseLayoutProps) {
return await SaplingLayout({
head: html`
${props.title}
`,
children: html`
${props.children}
`
});
}
```
## Layout Properties
Layouts accept properties that extend Sapling's base `LayoutProps`:
```typescript
type LayoutProps = {
head?: unknown; // Additional head content
bodyClass?: string; // Classes to apply to the body
children?: unknown; // Page content
unoConfig?: unknown; // UnoCSS configuration
};
```
## Using Components in Layouts
Layouts can import and use components to create consistent page structures:
```typescript
import { Nav } from "../components/Nav.ts";
import { Footer } from "../components/Footer.ts";
export default async function Layout(props: BaseLayoutProps) {
return await SaplingLayout({
// ... other props
children: html`
${Nav()}
${props.children}
${Footer()}
`
});
}
```
## Styling with UnoCSS
Sapling layouts can include UnoCSS configuration for consistent styling:
```typescript
import { config } from "../uno.config.ts";
export default async function Layout(props: BaseLayoutProps) {
return await SaplingLayout({
unoConfig: config,
bodyClass: `font-sans @dark:bg-black @dark:text-white ${props.bodyClass ?? ''}`,
// ... other props
});
}
```
## Nested Layouts
You can create specialized layouts that extend your base layout:
```typescript
import BaseLayout from "./Layout.ts";
export default async function DocsLayout(props: DocsLayoutProps) {
return await BaseLayout({
...props,
children: html`
${props.children}
`
});
}
```
## Using Layouts in Pages
To use a layout in a page component:
```typescript
import Layout from "../layouts/Layout.ts";
export default async function HomePage() {
return await Layout({
title: "Home - My Site",
description: "Welcome to my website",
children: html`
Welcome!
`
});
}
```
## Best Practices
1. **Type Safety**: Define proper TypeScript interfaces for your layout props
2. **Modularity**: Break down layouts into reusable components
3. **Flexibility**: Make layouts adaptable with optional props
4. **Dark Mode**: Include dark mode support using the `@dark:` prefix
5. **Performance**: Keep layouts lightweight and avoid unnecessary nesting
---
title: Acknowledgements
description: Special thanks to the projects and people that make Sapling possible
publishedAt: "2024-01-01"
---
# Acknowledgements
Sapling stands on the shoulders of giants. We'd like to acknowledge and thank the following projects and communities that make Sapling possible:
## Core Technologies
- [UnoCSS](https://github.com/unocss/unocss) - We use UnoCSS under the hood for atomic CSS.
- [marked](https://github.com/markedjs/marked) - Our markdown parser.
- [shiki](https://github.com/shikijs/shiki) - The code highlighter for syntax highlighting.
## Inspiration
Here are some projects that have inspired us:
- [Astro](https://astro.build) - Astro's approach to building modern websites with zero JavaScript by default was a core concept that we wanted to be central to Sapling.
- [Hono](https://hono.dev) - We recommend Hono for routing and we use it in most of our examples because we are huge fans of its simplicity and API design.
- [Fresh](https://fresh.deno.dev) - Fresh's SSR-first, no build by default approach was a huge reason why Sapling is structured the way it is.
- [11ty](https://www.11ty.dev) - 11ty's approach to not tie the framework to the bundler was a big inspiration for Sapling.
- Our [``](/docs/sapling-island) component is heavily inspired by Astro's islands implementation and 11ty's [is-land](https://github.com/11ty/is-land) web component.
- [Svelte Docs](https://svelte.dev/docs) - Thanks Svelte for a good docs page layout.
Each of these projects are amazing in their own right and we're so thankful they exist as fully open source projects that we can all learn from.
## Runtime Support
- **[Deno](https://deno.land)**
- **[Node.js](https://nodejs.org)**
- **[Bun](https://bun.sh)**:
- **[Cloudflare Workers](https://workers.cloudflare.com)**
## Community
We're grateful to all the developers and users who have contributed to Sapling through:
- Bug reports and fixes
- Feature suggestions
- Documentation improvements
- Community support
## License
Sapling is open source software licensed under the MIT license. We believe in the power of open source and are committed to keeping Sapling free and open for everyone.
---
title: Quick Start (Node.js)
description: Get started with Sapling using Node.js
publishedAt: "2024-01-01"
---
# Quick Start with Node.js
This guide will help you create your first Sapling website using Node.js.
## Prerequisites
- [Node.js](https://nodejs.org) (version 18 or higher)
- npm (comes with Node.js)
## Create a New Project
```bash
npm create sapling@latest
```
Select the "Basics" template.
## Start the Development Server
```bash
npm run dev
```
Your site is now running at [http://localhost:8080](http://localhost:8080)!
## Project Structure
```bash
my-website/
├── components/ # Reusable UI components
├── layouts/ # Page layouts
├── pages/ # Your pages/routes
├── static/ # Static assets
├── package.json # Project configuration
└── index.ts # Entry point
```
## Create Your First Page
1. Create a new file `pages/about.ts`:
```typescript
import Layout from "../layouts/Layout.ts";
import { html } from "@hono/hono/html";
export default async function About() {
return await Layout({
children: html`
`,
});
}
```
2. Add the route in `index.ts` underneath the `Home` route:
```typescript
site.get("/about", async (c) => c.html(await About()));
```
Visit [http://localhost:8080/about](http://localhost:8080/about) to see your new page!
## Next Steps
- Learn about [Routing](/docs/routing)
- Explore [Layouts](/docs/layouts)
- Check out the [Examples](https://github.com/saplingjs/examples)