Search
Code1,917
- [Hono](https://hono.dev/) for routing- React for SSR'd components- Vanilla CSS- A bit of client-side JS via `<script>` (e.g. filter posts by tag)- Images, videos and whatnot are drag 'n' dropped into markdown (and- [Hono](https://hono.dev/) for routing- React for SSR'd components- Vanilla CSS- A bit of client-side JS via `<script>` (e.g. filter posts by tag)- Images, videos and whatnot are drag 'n' dropped into markdown (anddescription: >- Some vals experienced lockfile and React errors due to breaking changes in esm.shYesterday [esm.sh](https://esm.sh) deployed breaking changes that caused two kinds of errors for our users: lockfile errors and client-side React errors. The first error was resolved by esm.sh rolling back the change related to React version pinning. The second error was resolved by us manually regenerating all affected lockfiles. We are working to upgrade our systems with the esm.sh maintainer to ensure this doesn't happen again.- **12:00am ET** - Vals started experiencing lockfile errors. Some vals using client-side React stopped working.- **4:09am ET** - esm.sh maintainer [announced the changes in the Val Town Discord](https://discord.com/channels/1020432421243592714/1020432421243592717/1329014642223218741)- **9:36am ET** - esm.sh begins [rolling back the change related to React version pinning](https://github.com/esm-dev/esm.sh/pull/1032)- **10:34am ET** - All client-side React vals resume working- **10:39am ET** - We stopped sending unnecessary lockfile error emails to users (because the lockfiles would automatically regenerate)#### Client-side React errorsThe client-side React errors were unrelated to the lockfile errors, but were also bundled into the v136 esm.sh release. In v136, esm.sh stopped pinning `react-dom` to use the same version of `react` automatically, which is behavior that many vals were unintentionally relying on. This issue was more easily resolved, when by simply having [esm.sh roll back that change](https://github.com/esm-dev/esm.sh/pull/1032).# vtrr — Val Town React RouterA zero-boilerplate fullstack React framework for [Val Town](https://val.town). Server-side render React apps with file-based loaders and actions, client-side hydration, and single-fetch navigation — all in a few lines of code. participant Hono as Hono Server participant RR as React Router (SSR) participant Loader as Loader / Action RR-->>Hono: Static context (loaderData, appHtml) Hono-->>Browser: Full HTML + hydration data + client script Note over Browser,Loader: Client Hydration Browser->>Browser: Load /__client.js Browser->>Browser: hydrateRoot() with React Router Note over Browser,Loader: Client-Side Navigation (Single Fetch) Browser->>Hono: GET /new-page (X-Data-Request: true) Hono-->>Browser: JSON response Browser->>Browser: React Router updates UI``````tsx/** @jsxImportSource https://esm.sh/react@18.2.0 */import { useLoaderData } from "https://esm.sh/react-router@7.5.0?deps=react@18.2.0,react-dom@18.2.0";import { defineRoutes } from "https://esm.town/v/stevekrouse/vtrr/routes.ts";That's it. SSR, hydration, and client-side navigation — all wired up automatically. E -->|uses| H[Hono] E -->|uses| I[React Router SSR] E -->|serves| J["/__client.js (bootstrap)"] E -->|serves| K["/__src/* (source files)"] J -->|loads| M[client-runtime.ts] M -->|hydrates with| N[React Router Browser] M -->|implements| O[Single-Fetch dataStrategy]| `server.ts` | Hono server setup, SSR rendering, static file serving, hydration data injection || `routes.ts` | `defineRoutes()` — transforms user route configs into React Router route objects (server: real imports, client: stubs) || `client-runtime.ts` | Browser hydration script with single-fetch `dataStrategy` for client-side navigation || `types.ts` | TypeScript interfaces (`AppOptions`, `UserRouteConfig`, re-exported RR types) |Converts your route config into React Router-compatible route objects. Loaders and actions are specified as **string paths** (resolved to dynamic imports on the server, stubbed on the client).**Loader/action resolution:**- **String paths** (e.g. `"./routes/Home.loader.ts"`) — resolved relative to `VALTOWN_ENTRYPOINT` on the server; stubbed on the client- **`import.meta.url`** — use when the loader is exported from the same file (great for single-file apps) index?: boolean; // Index route flag Component?: React.ComponentType; // React component loader?: string; // Path to loader module children?: UserRouteConfig[]; // Nested routes errorElement?: React.ReactElement; // Error boundary}Client-side navigations use a **single HTTP request** per navigation instead of one request per loader. The client sends `X-Data-Request: true` header → the server runs all matched loaders → returns all data as one JSON response.graph LR A[Client Navigation] --> B["fetch(url, {headers: X-Data-Request: true})"] B --> C[Server runs ALL matched loaders] C --> D["JSON { loaderData, actionData }"] D --> E[React Router updates matched routes]```- **Hono for HTTP** — lightweight, fast, and gives users an escape hatch via `setup()` for API routes and middleware- **String-based loader/action paths** — loaders/actions never ship to the client; only the server dynamically imports them- **Single-fetch on navigation** — one request per navigation instead of waterfall requests per loader- **`/__src/*` file serving** — project source files served under a prefix to avoid conflicts with page routes- **Hydration via `window.__staticRouterHydrationData`** — server injects loader data, client picks it up seamlessly# vtrr — Val Town React RouterA zero-boilerplate fullstack React framework for [Val Town](https://val.town). Server-side render React apps with file-based loaders and actions, client-side hydration, and single-fetch navigation — all in a few lines of code. participant Hono as Hono Server participant RR as React Router (SSR) participant Loader as Loader / Action RR-->>Hono: Static context (loaderData, appHtml) Hono-->>Browser: Full HTML + hydration data + client script Note over Browser,Loader: Client Hydration Browser->>Browser: Load /__client.js Browser->>Browser: hydrateRoot() with React Router Note over Browser,Loader: Client-Side Navigation (Single Fetch) Browser->>Hono: GET /new-page (X-Data-Request: true) Hono-->>Browser: JSON response Browser->>Browser: React Router updates UI``````tsx/** @jsxImportSource https://esm.sh/react@18.2.0 */import { useLoaderData } from "https://esm.sh/react-router@7.5.0?deps=react@18.2.0,react-dom@18.2.0";import { defineRoutes } from "https://esm.town/v/stevekrouse/vtrr/routes.ts";That's it. SSR, hydration, and client-side navigation — all wired up automatically. E -->|uses| H[Hono] E -->|uses| I[React Router SSR] E -->|serves| J["/__client.js (bootstrap)"] E -->|serves| K["/__src/* (source files)"] J -->|loads| M[client-runtime.ts] M -->|hydrates with| N[React Router Browser] M -->|implements| O[Single-Fetch dataStrategy]| `server.ts` | Hono server setup, SSR rendering, static file serving, hydration data injection || `routes.ts` | `defineRoutes()` — transforms user route configs into React Router route objects (server: real imports, client: stubs) || `client-runtime.ts` | Browser hydration script with single-fetch `dataStrategy` for client-side navigation || `types.ts` | TypeScript interfaces (`AppOptions`, `UserRouteConfig`, re-exported RR types) |Converts your route config into React Router-compatible route objects. Loaders and actions are specified as **string paths** (resolved to dynamic imports on the server, stubbed on the client).**Loader/action resolution:**- **String paths** (e.g. `"./routes/Home.loader.ts"`) — resolved relative to `VALTOWN_ENTRYPOINT` on the server; stubbed on the client- **`import.meta.url`** — use when the loader is exported from the same file (great for single-file apps) index?: boolean; // Index route flag Component?: React.ComponentType; // React component loader?: string; // Path to loader module children?: UserRouteConfig[]; // Nested routes errorElement?: React.ReactElement; // Error boundary}Client-side navigations use a **single HTTP request** per navigation instead of one request per loader. The client sends `X-Data-Request: true` header → the server runs all matched loaders → returns all data as one JSON response.graph LR A[Client Navigation] --> B["fetch(url, {headers: X-Data-Request: true})"] B --> C[Server runs ALL matched loaders] C --> D["JSON { loaderData, actionData }"] D --> E[React Router updates matched routes]```- **Hono for HTTP** — lightweight, fast, and gives users an escape hatch via `setup()` for API routes and middleware- **String-based loader/action paths** — loaders/actions never ship to the client; only the server dynamically imports them- **Single-fetch on navigation** — one request per navigation instead of waterfall requests per loader- **`/__src/*` file serving** — project source files served under a prefix to avoid conflicts with page routes- **Hydration via `window.__staticRouterHydrationData`** — server injects loader data, client picks it up seamlessly/** @jsxImportSource https://esm.sh/react@18.2.0 */import { useState } from "https://esm.sh/react@18.2.0"; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // Client-side validation to match server-side rules if (username.length < 3 || username.length > 50) {| --- | --- || **[`basic-html-starter`](https://www.val.town/x/templates/basic-html-starter)** *(you're here)* | Static HTML pages. Sprinkle of vanilla JS. No server-side rendering. || [`hono-jsx-starter`](https://www.val.town/x/templates/hono-jsx-starter) | Dynamic server-rendered HTML with JSX. API routes. No React, no SPA. || [`react-hono-starter`](https://www.val.town/x/templates/react-hono-starter) | Client-side React SPA with a Hono API backend. |# React + TanStack + Hono Val Town Project### Frontend- React 19- TanStack Router (code-first routing)- Message board with persistent storage- Client-side routing- Optimistic updates- Server-side data injection- Type-safe database operations│ └── index.ts # Main Hono application├── frontend/ # React app running in browser│ ├── components/ # React components│ ├── lib/ # Utilities and hooks- `GET /` - Serves the React application with initial data- `GET /api/messages` - Fetch all messages (JSON) onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["messages"] }); },This template is a classic client-side-only React app. This HTML file has a `<div id="root"></div>`, which is where we mount the React app.This HTML file imports `/frontend/index.tsx` from `/public/index.tsx`, which is the **entrypoint** for all frontend JavaScript, including all the React. It is not a problem that it imports a file with a `.tsx` extension becaues browsers ignore file extensions. They only pay attention to content-types, which is great, because all these files will be returned as transpiled JS with the appropriate JS content type by [stevekrouse/utils/serve-public](https://www.val.town/x/stevekrouse/utils/branch/main/code/serve-public/README.md).)This file is the **entrypoint** for frontend JavaScript. It imports the React app from `/frontend/components/App.tsx` and mounts it on `<div id="root"></div>`.This directory is where the React components are stored. They're pretty standard client-side React components.This backend HTTP server is responsible for serving all static assets to the browser to render the app, including HTML, JavaScript (including all client-side React), CSS, and even the favicon SVG.In a normal server environment, you would likely use a middleware [like this one](https://hono.dev/docs/getting-started/nodejs#serve-static-files) to serve static files. Some frameworks or deployment platforms automatically make any content inside a `public/` folder public. We *bootstrap* `index.html` with some initial data from the server, so that it gets dynamically injected JSON data without having to make another round-trip request to the server to get that data on the frontend. This is a common pattern for client-side rendered apps.This app has two CRUD API routes: for reading and inserting into the messages table. They both speak JSON, which is standard. They import their functions from `/backend/database/queries.ts`. These routes are called from the React app to refresh and update data.This template is a classic client-side-only React app.This HTML file has a `<div id="root"></div>`, which is where we mount the Reactapp.This HTML file imports `/frontend/index.tsx` from `/public/index.tsx`, which isthe **entrypoint** for all frontend JavaScript, including all the React. It isnot a problem that it imports a file with a `.tsx` extension becaues browsersThis file is the **entrypoint** for frontend JavaScript. It imports the Reactapp from `/frontend/components/App.tsx` and mounts it onThis directory is where the React components are stored. They're pretty standardclient-side React components.This backend HTTP server is responsible for serving all static assets to thebrowser to render the app, including HTML, JavaScript (including all client-sideReact), CSS, and even the favicon SVG.request to the server to get that data on the frontend. This is a common patternfor client-side rendered apps.table. They both speak JSON, which is standard. They import their functions from`/backend/database/queries.ts`. These routes are called from the React app torefresh and update data.# React Hono Val Town Project Starter TemplateThe entrypoint of this app is [`index.ts`](./index.ts), which is the Hono HTTPserver, which serves the HTML, CSS, and JS, which run client-side.This template is a classic client-side-only React app. This HTML file has a `<div id="root"></div>`, which is where we mount the React app.This HTML file imports `/frontend/index.tsx` from `/public/index.tsx`, which is the **entrypoint** for all frontend JavaScript, including all the React. It is not a problem that it imports a file with a `.tsx` extension becaues browsers ignore file extensions. They only pay attention to content-types, which is great, because all these files will be returned as transpiled JS with the appropriate JS content type by [stevekrouse/utils/serve-public](https://www.val.town/x/stevekrouse/utils/branch/main/code/serve-public/README.md).)This file is the **entrypoint** for frontend JavaScript. It imports the React app from `/frontend/components/App.tsx` and mounts it on `<div id="root"></div>`.This directory is where the React components are stored. They're pretty standard client-side React components.# React Hono Val Town Project Starter TemplateThe entrypoint of this app is `backend/index.ts`, which is the Hono HTTP server, which serves the HTML, CSS, and JS, which run client-side.If you'd like to make a template with other technologies choices (ie no Hono or no React), please create a new template and we can link to it from this one as another way to go. Eventually, we'd like to be many templates that show how to use Projects. The next one I'm personally excited about making is a fullstack React Router 7 one. [This one](https://www.val.town/x/just_be/reactRouter7) is a good start, but I'd like to rebuild the message board app from this template in that format.