Val Town Development Skills & Learnings
Val Town is a serverless platform for running JavaScript/TypeScript code with a unique deployment model focused on "vals" (individual code modules). This document captures key learnings from converting a static site to a React TSX application deployed on Val Town.
Vals : Individual code modules that can be HTTP endpoints, cron jobs, or email handlers
File-based deployment : Code is deployed by pushing files via the vt CLI tool
Branch-based versioning : Each val can have multiple branches (main, dev, etc.)
No traditional server management : Platform handles scaling, hosting, and infrastructure
# Check status of local changes
vt status
# Push changes to deploy
vt push
# Open deployed val in browser
vt browse
# Clone existing vals
vt clone <valUri> [targetDir]
# Create new vals
vt create <valName> [targetDir]
3. React Integration in Val Town
import React , { useState } from "https://esm.sh/react@18.2.0" ;
import { createRoot } from "https://esm.sh/react-dom@18.2.0/client" ;
Pinned versions : Always pin React versions to avoid breaking changes
ESM.sh imports : Use https://esm.sh for all external dependencies
No build step : Direct browser execution of TSX files
Functional components with hooks
TypeScript interfaces for props and state
TailwindCSS for styling (CDN-based)
Client-side rendering with createRoot
4. Progressive Enhancement Pattern
<noscript>
<!-- Graceful degradation for users without JavaScript -->
<div>Content requiring JavaScript...</div>
</noscript>
<div id="root"></div>
<script type="module" src="index.tsx"></script>
SEO friendly : Search engines can index the fallback content
Accessibility : Users with disabled JavaScript still get useful content
Performance : Fast initial load with progressive enhancement
5. TypeScript Configuration for Val Town
Deno Configuration (deno.json)
{
"compilerOptions": {
"noImplicitAny": false,
"strict": true,
"lib": ["dom", "dom.iterable", "deno.ns", "deno.unstable"]
},
"lint": {
"rules": {
"exclude": ["no-explicit-any", "no-import-prefix"]
}
}
}
Deno-specific libs : Include deno.ns and deno.unstable
Import prefix rules : Exclude no-import-prefix to allow ESM.sh imports
Strict mode : Enable but allow any types where needed
6. TailwindCSS Integration
<script src="https://cdn.twind.style" crossorigin></script>
<div className=
"min-h-screen bg-black text-white font-mono p-4 flex items-center justify-center" >
<div className="w-full max-w-2xl space-y-8">
{/* Content */}
</div>
</div>
7. File Organization Best Practices
├── index.html # No-JS fallback + React container
├── index.tsx # React app entry point
├── App.tsx # Main component
├── deno.json # Deno configuration
├── CRUSH.md # Agent development guidelines
└── .gitignore # Exclude .vt/, .crush/, etc.
Entry points : index.tsx for React apps
Components : PascalCase (App.tsx, Header.tsx)
HTML : index.html as the primary file
8. Error Handling & Debugging
No console.log in production (use proper logging)
Limited debugging tools compared to traditional servers
Network requests must be HTTPS
No server-side file system access
Use try/catch for async operations
Provide user-friendly error messages
Test with vt status before pushing
Use vt tail to monitor logs in production
Code splitting : Keep components focused and small
Lazy loading : Import components only when needed
Bundle size : Monitor via browser dev tools
Caching : Leverage browser caching for assets
Cold starts : First request may be slower
Memory limits : Be mindful of resource usage
Concurrent requests : Platform handles scaling automatically
# Check TypeScript compilation
deno check
# Run linter
deno lint *.tsx
# Test locally (serve static files)
python -m http.server 8000
Make changes locally
Test with deno check and deno lint
Check vt status for pending changes
vt push to deploy
vt browse to verify deployment
11. Security Considerations
No secrets in code : Use environment variables
Input validation : Sanitize all user inputs
HTTPS only : All external requests must use HTTPS
CORS handling : Configure appropriate CORS headers
Environment variables : Access via Deno.env.get()
No file system access : Cannot read/write server files
Sandbox environment : Isolated execution context
export default async function (req : Request ): Promise <Response > {
return new Response ("Hello World" );
}
export default async function ( ): Promise <void > {
}
vt status - Check deployment status
vt push - Deploy changes
vt pull - Sync remote changes
vt browse - Open in browser
vt create - Create new vals
vt clone - Clone existing vals
14. Common Pitfalls & Solutions
Binary files : Cannot upload binary files > certain size
Database files : SQLite files cannot be deployed
Empty files : Files must have content
Version pinning : Always pin dependency versions
ESM.sh URLs : Use full URLs for imports
Lint conflicts : Configure linter to allow Val Town patterns
JSX transform : Must use @jsxImportSource
Component mounting : Use createRoot for React 18
Styling conflicts : TailwindCSS works but may need custom config
Val Town is perfect for : Small web apps, APIs, scheduled tasks, and rapid prototyping
React works great : With proper JSX configuration and pinned versions
Progressive enhancement : Essential for accessibility and SEO
Deployment is simple : Just vt push after local testing
Platform limitations : Must design around serverless constraints
TypeScript first : Strong typing is encouraged and well-supported
ESM.sh ecosystem : Rich ecosystem of packages available via ESM.sh
Advanced patterns : Custom hooks, context providers, routing
Database integration : Val Town's built-in SQLite support
Real-time features : WebSockets and server-sent events
Multi-val architectures : Coordinating between multiple vals
Performance optimization : Bundle analysis and code splitting
Testing strategies : Unit and integration testing approaches
Documented on: 2025-01-19
Platform: Val Town
Project: mcp2cli React TSX conversion
skills/val-town-development.md