--- 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` ${siteContent.siteTitle} `, bodyClass: "text-gray-900 font-sans min-h-screen @dark:bg-gray-900 @dark:text-white", children: html`

${siteContent.hero.title}

${siteContent.hero.subtitle}

`, })); }); ``` ## 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 (

{user.name}

Age: {user.age}

View Profile
); } ``` ### 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`

About Us

Welcome to our website!

Go back home
`, }); } ``` 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`

About Us

Welcome to our website!

Go back home
`, }); } ``` 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)