# Guide to Writing Hype Apps Hype is a thin, opinionated wrapper around [Hono](https://hono.dev) for fast prototyping with Bun. It gives you file-based routing, automatic TypeScript transpilation, SSE, inline CSS/JS helpers, and a default HTML5 layout — all without a build step. Since `Hype extends Hono`, every Hono API (`get`, `post`, `use`, `on`, etc.) works out of the box. ## Table of Contents - [Getting Started](#getting-started) - [Project Structure](#project-structure) - [SSR Apps](#ssr-apps) - [SPA Apps](#spa-apps) - [Routing](#routing) - [Layouts](#layouts) - [Pages](#pages) - [Client-Side JavaScript](#client-side-javascript) - [Styling](#styling) - [Server-Sent Events (SSE)](#server-sent-events-sse) - [The `fe()` Helper](#the-fe-helper) - [Inline `css` and `js` Tags](#inline-css-and-js-tags) - [Custom API Routes](#custom-api-routes) - [Sub-Routers](#sub-routers) - [Static Files](#static-files) - [Configuration](#configuration) - [Environment Variables](#environment-variables) - [Path Aliases](#path-aliases) - [Utility Functions](#utility-functions) - [Recipes](#recipes) --- ## Getting Started ```sh # add hype to your project bun add @because/hype # create your server entry point mkdir -p src/server src/pages src/css src/client pub ``` Add scripts to `package.json`: ```json { "scripts": { "start": "bun run src/server/index.ts", "dev": "bun run --hot src/server/index.ts" } } ``` Add the required `tsconfig.json`: ```json { "compilerOptions": { "lib": ["ESNext", "DOM"], "target": "ESNext", "module": "Preserve", "moduleDetection": "force", "jsx": "react-jsx", "jsxImportSource": "hono/jsx", "allowJs": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "verbatimModuleSyntax": true, "noEmit": true, "strict": true, "skipLibCheck": true, "noFallthroughCasesInSwitch": true, "noUncheckedIndexedAccess": true, "noImplicitOverride": true, "baseUrl": ".", "paths": { "$*": ["src/server/*"], "#*": ["src/client/*"], "@*": ["src/shared/*"] } } } ``` Create your server: ```ts // src/server/index.ts import { Hype } from '@because/hype' const app = new Hype() export default app.defaults ``` Create your first page: ```tsx // src/pages/index.tsx export default () => (

Hello, world!

) ``` Run it: ```sh bun dev ``` That's it. You have a server-rendered page at `http://localhost:3000`. --- ## Project Structure ``` . ├── package.json ├── tsconfig.json ├── pub/ # Static files (served as-is at /) │ └── img/ │ └── logo.png # => /img/logo.png ├── src/ │ ├── server/ │ │ └── index.ts # Server entry point │ ├── pages/ │ │ ├── index.tsx # => GET / │ │ ├── about.tsx # => GET /about │ │ └── _layout.tsx # Custom layout (optional) │ ├── client/ │ │ └── main.ts # Client JS (auto-included by default layout) │ ├── shared/ │ │ └── types.ts # Shared between server and client │ └── css/ │ └── main.css # App CSS (auto-included by default layout) ``` - `src/pages/` — SSR pages, one file per route - `src/client/` — Client-side TypeScript, transpiled and bundled on demand - `src/shared/` — Isomorphic code, available to both server and client - `src/css/` — Stylesheets - `src/server/` — Server-only code - `pub/` — Static assets served directly --- ## SSR Apps SSR is the default mode. Pages in `src/pages/` are server-rendered on every request using Hono's JSX engine and wrapped in a layout. ### Minimal SSR app ```ts // src/server/index.ts import { Hype } from '@because/hype' const app = new Hype() export default app.defaults ``` ```tsx // src/pages/index.tsx export default () => (

Welcome

This is server-rendered HTML.

About
) ``` ```tsx // src/pages/about.tsx export default () => (

About

This website was made using futuristic internet technologies.

<= Back
) ``` The default layout automatically includes `src/css/main.css` and `src/client/main.ts`, wraps your page content in ``, ``, and `
...
`. ### Accessing the request Page components receive `c` (the Hono context) and `req` (the Hono request) as props: ```tsx // src/pages/greet.tsx export default ({ req }) => (

Hello, {req.query('name') ?? 'stranger'}!

) ``` Visit `/greet?name=Chris` to see `Hello, Chris!`. For the full Hono context: ```tsx // src/pages/debug.tsx export default ({ c, req }) => { const ua = req.header('user-agent') return (

Request Info

URL: {req.url}

User-Agent: {ua}

) } ``` --- ## SPA Apps For a single-page app with client-side rendering, disable the default layout and provide your own HTML shell: ```ts // src/server/index.ts import { Hype } from '@because/hype' const app = new Hype({ layout: false }) export default app.defaults ``` ```tsx // src/pages/index.tsx export default () => ( My SPA
{children}
) export default Layout ``` The layout receives: - `children` — the rendered page content - `title` — page title (defaults to `'hype'`) - `props` — the `HypeProps` passed to the constructor (useful for conditional CSS) ### No layout Disable the layout entirely for full control (used in SPA mode): ```ts const app = new Hype({ layout: false }) ``` --- ## Pages Pages are `.tsx` files in `src/pages/` that export a default function (or raw JSX). ### Function export (recommended) ```tsx // src/pages/index.tsx export default ({ c, req }) => (

Home

) ``` ### Static JSX export ```tsx // src/pages/about.tsx export default (

About

) ``` ### Async data in pages Since pages render on the server, you can use top-level await: ```tsx // src/pages/users.tsx const users = await fetch('https://api.example.com/users').then(r => r.json()) export default () => (

Users

) ``` Note: top-level data is fetched once at import time and cached. For per-request data, use the `c` context: ```tsx // src/pages/profile.tsx export default async ({ c, req }) => { const userId = req.query('id') const user = await fetch(`https://api.example.com/users/${userId}`).then(r => r.json()) return (

{user.name}

) } ``` ### Private pages Prefix a file with `_` to prevent it from being served: ``` src/pages/_layout.tsx # not a route — used as the layout src/pages/_helpers.tsx # not a route — internal helpers src/pages/index.tsx # GET / ``` --- ## Client-Side JavaScript ### Automatic transpilation TypeScript files in `src/client/` and `src/shared/` are automatically transpiled and bundled by Bun when requested by the browser. The URL maps directly to the file path: | File | URL | |------|-----| | `src/client/main.ts` | `/client/main.ts` | | `src/client/app.tsx` | `/client/app.js` | | `src/shared/utils.ts` | `/shared/utils.ts` | You can request `.ts` or `.js` extensions — Hype resolves `.ts` and `.tsx` files automatically. ### Module imports Client-side files can import from each other using relative paths: ```ts // src/client/main.ts import { initBurger } from './burger' initBurger() ``` ```ts // src/client/burger.ts export function initBurger() { document.addEventListener('click', (ev) => { const el = (ev?.target as HTMLElement).closest('.burger') as HTMLImageElement if (!el) return el.src = '/img/bite.png' }) } ``` Imports are bundled — the full dependency graph is included in the output, so the browser only needs one request. ### The default layout auto-includes `main.ts` When using the default layout, `src/client/main.ts` is automatically loaded as a module. Just create the file and it works. --- ## Styling ### External CSS Put your styles in `src/css/main.css`. The default layout auto-includes it: ```css /* src/css/main.css */ section { max-width: 500px; margin: 0 auto; } ``` You can also serve additional CSS files from `src/css/`: ```html ``` ### Pico CSS Enable the bundled [Pico CSS](https://picocss.com) for classless styling: ```ts const app = new Hype({ pico: true }) ``` Or include it in a custom layout: ```html ``` ### CSS Reset Enable the bundled CSS reset (Josh W. Comeau's reset): ```ts const app = new Hype({ reset: true }) ``` Or include it in a custom layout: ```html ``` ### Combining options ```ts const app = new Hype({ pico: true, reset: true }) ``` ### Inline CSS Use the `css` template tag for scoped inline styles in any page: ```tsx import { css } from '@because/hype' export default () => (
{css` .hero { background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 4rem 2rem; text-align: center; } `}

Welcome

) ``` This renders a `