π A revolutionary meta-framework that seamlessly integrates multiple frontend technologies with unprecedented simplicity.
Break free from technology stack lock-in while maintaining elegant simplicity. Experience the power of running React, Vue, and other frameworks together with ease.
β οΈ Preview Release: This project is currently in preview stage with API subject to changes. Some features mentioned may be under development.
- π« Design Philosophy
- β¨ Why Web Widget?
- π Quick Start
- ποΈ Core Architecture
- π₯ Key Features
- π Project Structure
- π Documentation
- π€ Community
Our framework is built on the core principle that powerful technology should be intuitive to use:
- Two File Types: Just
@route.*
and@widget.*
- that's all you need to learn - Minimal Configuration: Works with sensible defaults and simple setup
- Familiar Syntax: Use the frameworks you already know and love
- Intuitive APIs: If it feels natural, it probably works
- Multi-Framework: React 19, Vue 3, Vue 2 - with more frameworks coming soon
- Web Standards: Built on solid foundations that won't become obsolete
- Real-World Ready: Designed to handle production applications
- Future Proof: Open architecture that evolves with the web platform
"The best technology is the one you don't have to think about" - This is our guiding principle.
The Challenge: Modern web development often forces teams into framework silos, making technology evolution difficult and risky. Monolithic frontend architectures become increasingly hard to maintain and upgrade.
The Web Widget Approach: A meta-framework that embraces framework diversity while maintaining simplicity.
- π Technology Freedom: Use the right framework for each component, not the entire application
- β‘ Incremental Adoption: Introduce new frameworks gradually without big-bang rewrites
- π§© Component Interoperability: Mix React and Vue components seamlessly
- π Standards Foundation: Built on Web Standards that evolve with the platform
- π― Developer Experience: Simple concepts that scale to complex applications
Applications like insmind.com and gaoding.com demonstrate these benefits in practice:
- Legacy component preservation while adopting modern frameworks
- Zero-downtime migrations through incremental transitions
- Successful mixing of multiple technology stacks in production
Application | Architecture | Use Case |
---|---|---|
insmind.com | React pages + Vue 3 + Vue 2 components | "Seamlessly integrated legacy Vue 2 components with modern Vue 3 features" |
gaoding.com | React pages + Vue 2 + Lit components | "Migrated incrementally from Vue 2 to React without downtime" |
π‘ These examples showcase the framework's ability to handle complex multi-technology scenarios in real applications.
The approach works: Web Widget provides a practical path to multi-framework architecture without the usual complexity overhead.
Get started with Web Widget (preview release):
# Clone or download the example
git clone https://github.com/web-widget/web-widget.git
cd web-widget/examples/react # or examples/vue
# Install dependencies
npm install
# Start development
npm run dev
Framework | Status | Version |
---|---|---|
React | β Supported | 19.x |
Vue | β Supported | 3.x, 2.x |
Svelte | π§ Coming Soon | - |
Solid | π§ Coming Soon | - |
Angular | π§ Planned | - |
π‘ Preview Status: Web Widget is currently in preview. A dedicated CLI tool (
create-web-widget-app
) is planned for future releases.
Try online examples:
Example | Description | Live Demo |
---|---|---|
React | React pages with React + Vue components | |
Vue | Vue pages with React + Vue components |
Web Widget's power comes from just two concepts - keeping it beautifully simple:
Server-side modules for rendering pages and handling HTTP requests.
// routes/index@route.tsx - Simple, yet it can do everything
import { defineRouteComponent } from '@web-widget/helpers';
import Counter from './components/Counter@widget.tsx';
export default defineRouteComponent(function HomePage() {
return (
<html>
<body>
<h1>Welcome to Web Widget</h1>
<Counter count={0} />
</body>
</html>
);
});
Isomorphic components that work on both server and client - the secret to our power.
// components/Counter@widget.tsx (React)
import { useState } from 'react';
export default function Counter({ count }: { count: number }) {
const [value, setValue] = useState(count);
return (
<div>
<button onClick={() => setValue((v) => v - 1)}>-</button>
<span>{value}</span>
<button onClick={() => setValue((v) => v + 1)}>+</button>
</div>
);
}
<!-- components/Counter@widget.vue (Vue) -->
<script setup lang="ts">
import { ref } from 'vue';
const props = defineProps<{ count: number }>();
const value = ref(props.count);
</script>
<template>
<div>
<button @click="value--">-</button>
<span>{{ value }}</span>
<button @click="value++">+</button>
</div>
</template>
The real power emerges when you effortlessly combine different frameworks:
// Mix React and Vue in the same page
import ReactCounter from './Counter@widget.tsx';
import VueCounter from './Counter@widget.vue';
import { toReact } from '@web-widget/vue';
const RVueCounter = toReact(VueCounter);
export default defineRouteComponent(function MixedPage() {
return (
<div>
<h2>React Component:</h2>
<ReactCounter count={0} />
<h2>Vue Component (as React):</h2>
<RVueCounter count={0} />
</div>
);
});
- Streaming SSR: Pages start rendering before all data is loaded
- Selective Hydration: Only interactive components hydrate on client
- Optimized Bundles: Server components reduce client-side JavaScript
- Zero Hydration Errors: End-to-end state caching eliminates SSR mismatches
- HTTP Caching: Standards-based page caching with stale-while-revalidate patterns
- Currently Supported: React 19, Vue 3, Vue 2 (Svelte, Solid, and more coming soon)
- Progressive Migration: Upgrade frameworks piece by piece
- Component Interop: Share components across different frameworks
- No Lock-in: Each component can use its preferred framework
- WinterCG Compatible: Runs in Node.js, Deno, Bun, and Edge environments (Currently only Node.js deployment examples are supported, other platforms coming soon)
- ESM Native: Modern module system with import maps
- Web APIs: Use standard fetch, streams, and crypto APIs everywhere
- Future Proof: Based on standards that won't become obsolete
- Production Module Sharing: Import Maps for optimal production performance
- Type Safe: Full TypeScript support out of the box
- File-based Routing: Intuitive routing with automatic route generation
- Error Boundaries: Comprehensive error handling and fallbacks
- Sensible Defaults: Simple setup with intelligent defaults that just work
- Smart Bundling: Automatic dependency deduplication and sharing
Web Widget solves SSR's biggest challenge: hydration mismatches. Our cache providers ensure server and client always render identical content.
// β Traditional SSR: Hydration mismatches
function UserProfile({ userId }) {
const [user, setUser] = useState(null); // Server: null, Client: fetched data
useEffect(() => {
fetchUser(userId).then(setUser); // Only runs on client
}, []);
return <div>{user?.name || 'Loading...'}</div>; // Different on server vs client
}
// β
Web Widget: Perfect hydration
function UserProfile({ userId }) {
const user = syncCacheProvider(`user-${userId}`, () => fetchUser(userId));
return <div>{user.name}</div>; // Identical on server and client
}
- Server: Execute data fetching, cache results
- Transfer: Automatically embed cached data in HTML
- Client: Read cached data, skip re-fetching
// Vue 3: Use asyncCacheProvider for top-level await
const userData = await asyncCacheProvider('user-profile', async () => {
return fetch('/api/user').then((r) => r.json());
});
// React: Use syncCacheProvider for hook-like behavior
const userData = syncCacheProvider('user-profile', async () => {
return fetch('/api/user').then((r) => r.json());
});
- β Zero Hydration Errors: Perfect server-client state synchronization
- β Simple Setup: Framework handles most configuration automatically
- β Optimal Performance: Data fetched once, used everywhere
- β Type Safe: Full TypeScript support with inferred types
my-web-widget-app/
βββ routes/
β βββ (components)/
β β βββ BaseLayout.tsx
β β βββ Counter@widget.tsx
β β βββ Counter@widget.vue
β βββ index@route.tsx
β βββ about@route.tsx
β βββ action/
β β βββ index@route.tsx
β β βββ functions@action.ts
β βββ api/
β βββ hello@route.ts
βββ public/
βββ entry.client.ts
βββ entry.server.ts
βββ routemap.server.json
βββ importmap.client.json
βββ package.json
βββ tsconfig.json
βββ vite.config.ts
Organized structure with clear separation of concerns.
File-System Routing
Web Widget supports file-system based routing conventions, automatically generating routemap.server.json
during development.
File Name | Route Pattern | Matching Paths |
---|---|---|
index@route.ts |
/ |
/ |
about@route.ts |
/about |
/about |
blog/[slug]@route.ts |
/blog/:slug |
/blog/foo , /blog/bar |
blog/[slug]/comments@route.ts |
/blog/:slug/comments |
/blog/foo/comments |
old/[...path]@route.ts |
/old/:path* |
/old/foo , /old/bar/baz |
[[lang]]/index@route.ts |
/{/:lang}? |
/ , /en , /zh-cn |
Create route groups using parentheses-wrapped folder names:
βββ routes
βββ (middlewares)
β βββ [...all]@middleware.ts # -> /:all*
βββ (vue2)
β βββ package.json
β βββ marketing@route.vue # -> /marketing
βββ (vue3)
βββ package.json
βββ info@route.vue # -> /info
Route Module Examples
// ./routes/index@route.tsx
import { defineRouteComponent, defineMeta } from '@web-widget/helpers';
import BaseLayout from './components/BaseLayout';
export const meta = defineMeta({
title: 'Home - Web Widget',
});
export default defineRouteComponent(function Page() {
return (
<BaseLayout>
<h1>Welcome to Web Widget</h1>
<p>This is a basic route module example</p>
</BaseLayout>
);
});
// ./routes/fetch@route.tsx
import {
defineRouteComponent,
defineRouteHandler,
defineMeta,
} from '@web-widget/helpers';
import BaseLayout from './components/BaseLayout';
interface PageData {
items: Array<{ title: string; url: string }>;
}
async function fetchData(url: URL): Promise<PageData> {
const response = await fetch(`${url.origin}/api/data`);
return response.json();
}
export const meta = defineMeta({
title: 'Data Fetching Example',
});
export const handler = defineRouteHandler<PageData>({
async GET(ctx) {
const data = await fetchData(new URL(ctx.request.url));
const response = ctx.html(data);
response.headers.set('X-Custom-Header', 'Hello');
return response;
},
});
export default defineRouteComponent<PageData>(function Page({ data }) {
return (
<BaseLayout>
<h1>Data Fetching</h1>
<ul>
{data.items.map((item, index) => (
<li key={index}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
</BaseLayout>
);
});
Routes are automatically configured based on your file structure. Web Widget generates the routing configuration during development, so you don't need to manually manage route mappings.
Advanced Routing Features
// routes/users/[id]@route.tsx
export default defineRouteComponent(function UserPage(props) {
const { id } = props.params;
return <div>User ID: {id}</div>;
});
// Simple redirects in route handlers
export const handler = defineRouteHandler({
async GET(ctx) {
if (shouldRedirect) {
return redirect('/new-path', 301);
}
return ctx.html();
},
});
// Route-level error handling
export const handler = defineRouteHandler({
async GET(ctx) {
if (!data) {
throw createHttpError(404, 'Not Found');
}
return ctx.html(data);
},
});
export const meta = defineMeta({
title: 'My Page',
description: 'Page description',
});
// Dynamic metadata in handlers
export const handler = defineRouteHandler({
async GET(ctx) {
const newMeta = mergeMeta(ctx.meta, {
title: `User: ${user.name}`,
});
return ctx.html(null, { meta: newMeta });
},
});
Widget Module Examples
// ./components/Counter@widget.tsx
import { useState } from 'react';
import styles from './Counter.module.css';
interface CounterProps {
count: number;
}
export default function Counter(props: CounterProps) {
const [count, setCount] = useState(props.count);
return (
<div className={styles.counter}>
<button onClick={() => setCount(count - 1)}>β</button>
<span className={styles.count}>{count}</span>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
<!-- ./components/Counter@widget.vue -->
<script setup lang="ts">
import { ref } from 'vue';
interface CounterProps {
count: number;
}
const props = defineProps<CounterProps>();
const count = ref(props.count);
</script>
<template>
<div class="counter">
<button @click="count--">β</button>
<span class="count">{{ count }}</span>
<button @click="count++">+</button>
</div>
</template>
<style scoped>
/*...*/
</style>
// ./routes/index@route.tsx
import { defineRouteComponent } from '@web-widget/helpers';
import BaseLayout from './components/BaseLayout';
import ReactCounter from './components/Counter@widget.tsx';
import VueCounter from './components/Counter@widget.vue';
import { toReact } from '@web-widget/vue';
const RVueCounter = toReact(VueCounter);
export default defineRouteComponent(function Page() {
return (
<BaseLayout>
<h1>Mixed Technology Stack Example</h1>
<h2>React Component:</h2>
<ReactCounter count={0} />
<h2>Vue Component:</h2>
<RVueCounter count={0} />
</BaseLayout>
);
});
Advanced Widget Features
// Server-only rendering
<StaticChart renderStage="server" data={chartData} />
// Client-only rendering
<InteractiveMap renderStage="client" location={coords} />
Access request data, parameters, and state in your components:
import { context } from '@web-widget/helpers/context';
export default function MyComponent() {
const { request, params, state } = context();
return <div>Current URL: {request.url}</div>;
}
Web Standards APIs
Full Web Standards support in all environments:
- Network:
fetch
,Request
,Response
,Headers
,WebSocket
- Encoding:
TextDecoder
,TextEncoder
,atob
,btoa
- Streams:
ReadableStream
,WritableStream
,TransformStream
- Crypto:
crypto
,CryptoKey
,SubtleCrypto
- Other:
AbortController
,URLPattern
,structuredClone
Advanced Import Maps Configuration
{
"imports": {
"react": "https://esm.sh/react@18.2.0",
"react-dom": "https://esm.sh/react-dom@18.2.0",
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client",
"vue": "https://esm.sh/vue@3.4.8",
"lodash": "https://esm.sh/lodash@4.17.21",
"date-fns": "https://esm.sh/date-fns@2.30.0",
"@company/ui-kit": "https://cdn.company.com/ui-kit@1.2.0/index.js",
"@company/analytics": "https://cdn.company.com/analytics@2.1.0/index.js"
},
"scopes": {
"/legacy/": {
"react": "https://esm.sh/react@17.0.2",
"react-dom": "https://esm.sh/react-dom@17.0.2"
}
}
}
Benefits in action:
- π¦ Automatic Deduplication: React loaded once, shared everywhere
- π CDN Optimization: Load popular libraries from fast CDNs
- π± Perfect Caching: Browser-native module caching
// In your components - just import naturally
import React from 'react'; // Shared via importmap
import { createApp } from 'vue'; // Shared via importmap
import MyComponent from '@components/MyComponent'; // Path mapping
// No build-time complexity, maximum runtime efficiency
The Web Platform Way: Instead of reinventing module sharing, we embrace the native solution that browsers are optimizing for.
Server Actions: Seamless Client-Server Integration
Web Widget's Server Actions feature allows you to call server-side functions directly from client components with unprecedented simplicity - no API endpoints, no fetch calls, just direct function invocation.
// Traditional approach: Complex API setup
// β Create API endpoint
export async function POST(request: Request) {
const data = await request.json();
return Response.json({ message: data.content, date: new Date() });
}
// β Client-side fetch calls
const handleClick = async () => {
const response = await fetch('/api/echo', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: inputValue }),
});
const result = await response.json();
setLog(JSON.stringify(result));
};
// β
Web Widget Server Actions: Pure simplicity
// Server function (functions@action.ts)
export const echo = async (content: string) => {
return {
message: content,
date: new Date().toISOString(),
respondent: 'server',
};
};
// Client component - call server function like any local function
const handleClick = async () => {
const result = await echo(inputValue); // Direct server call!
setLog(JSON.stringify(result));
};
- π― Direct Function Calls: Call server functions like local functions
- π‘ Automatic Networking: Framework handles HTTP requests/responses
- π Type Safety: End-to-end TypeScript support with full type checking
- β‘ Zero Boilerplate: No API routes, no fetch calls, no serialization code
- π Environment Detection: Server functions automatically run server-side only
routes/action/
βββ index@route.tsx # Route page
βββ Echo@widget.tsx # Interactive client component
βββ functions@action.ts # Server-only functions
βββ styles.css # Styling
// functions@action.ts - Server functions
export const echo = async (content: string) => {
// This code ONLY runs on the server
return {
message: content,
date: new Date().toISOString(),
respondent: typeof document === 'undefined' ? 'server' : 'client',
};
};
// Echo@widget.tsx - Client component
import { useState } from 'react';
import { echo } from './functions@action';
export default function EchoWidget() {
const [inputValue, setInputValue] = useState('');
const [result, setResult] = useState('');
const handleSubmit = async () => {
// Direct server function call - no fetch, no API endpoints!
const response = await echo(inputValue);
setResult(JSON.stringify(response, null, 2));
};
return (
<>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button onClick={handleSubmit}>Send to Server</button>
{result && <pre>{result}</pre>}
</>
);
}
- π― Simplicity: Write server logic as simple functions, not API endpoints
- β‘ Performance: Automatic request optimization and batching
- π Security: Server functions never expose internals to client
- π± Developer Experience: Unified development model across client/server
- π Productivity: Focus on business logic, not infrastructure code
The Future of Full-Stack Development: Server Actions represent a paradigm shift from thinking in terms of "API endpoints" to thinking in terms of "server functions" - making full-stack development as natural as writing single-tier applications.
HTTP Caching & Performance
Web Widget provides enterprise-grade HTTP caching using standard Cache Control headers:
- Cache-Control: Standard max-age, stale-while-revalidate, stale-if-error
- ETag & Conditional Requests: Efficient cache validation
- Pluggable Storage: Memory, Redis, disk, or custom backends via SharedCache
// index@route.tsx
export const config = {
cache: {
// Cache rendered pages using HTTP cache control directives
cacheControl: {
// Cache for 60 seconds in shared caches
sharedMaxAge: 60,
// Serve stale content for 7 days on errors
staleIfError: 604800,
// Background revalidation for 7 days
staleWhileRevalidate: 604800,
},
},
};
// ...
This mode requires integrating the @web-widget/middlewares/cache middleware
Project Setup & Configuration
my-web-widget-app/
βββ routes/
β βββ (components)/
β β βββ BaseLayout.tsx
β β βββ Counter@widget.tsx
β β βββ Counter@widget.vue
β βββ index@route.tsx
β βββ about@route.tsx
β βββ action/
β β βββ index@route.tsx
β β βββ functions@action.ts
β βββ api/
β βββ hello@route.ts
βββ public/
βββ entry.client.ts
βββ entry.server.ts
βββ routemap.server.json
βββ importmap.client.json
βββ package.json
βββ tsconfig.json
βββ vite.config.ts
{
"dependencies": {
"@web-widget/helpers": "^1.59.0",
"@web-widget/html": "^1.59.0",
"@web-widget/node": "^1.59.0",
"@web-widget/react": "^1.59.0",
"@web-widget/vue": "^1.59.0",
"@web-widget/web-router": "^1.59.0",
"@web-widget/web-widget": "^1.59.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"vue": "^3.4.8"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.1.1",
"@vitejs/plugin-vue": "^5.0.0",
"@web-widget/vite-plugin": "^1.59.0",
"vite": "^5.4.19"
}
}
routes/**/*@route.*
Route modules that only run on the server sideroutes/**/*@middleware.*
Middleware that only runs on the server sideroutes/**/*@action.*
Server functions that can be called directly from client componentsroutes/**/*@widget.*
Components that can interact with users, running simultaneously on both server and client sidesentry.client.ts
Client entry pointentry.server.ts
Server entry pointimportmap.client.json
Production module sharing configuration (used in builds only)routemap.server.json
Routing configuration file, automatically generated by development tools
Best Practices & Tips
- Technology Stack Isolation: Use widget modules to achieve isolation of different technology stack components
- Progressive Enhancement: Prioritize server-side rendering, add client-side interaction as needed
- Caching Strategy: Use lifecycle caching wisely to improve performance
- Error Handling: Implement comprehensive error boundaries and fallback solutions
- Type Safety: Make full use of TypeScript's type system
- Use
renderStage="server"
for static content that doesn't need interactivity - Use
renderStage="client"
for components that require browser APIs - Implement proper caching strategies for expensive operations
- Keep server components lightweight to improve SSR performance
- Group related routes using parentheses folders
- Share common components through widget modules
- Use TypeScript interfaces for prop typing
- Implement proper error boundaries at route level
- GitHub: web-widget/web-widget
- Issues: Report bugs or request features
- Discussions: Join the community
Join developers exploring the future of multi-framework architecture.
Web Widget represents a new approach to frontend architecture - one that embraces technology diversity rather than fighting it. By building on Web Standards and providing elegant abstractions, we're creating a path forward that doesn't require abandoning existing investments or limiting future choices.
- β Sustainable Architecture: Build applications that can evolve with the ecosystem
- β Reduced Risk: Incremental changes instead of big-bang migrations
- β Developer Choice: Use the best tool for each component, not one-size-fits-all
- β Future-Ready: Built on standards that will outlast individual frameworks
Ready to explore framework freedom?
Experience the elegant simplicity of multi-framework architecture. Build your future with Web Widget.
"Simplicity is the ultimate sophistication" - Leonardo da Vinci