🌟 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.
Our framework is built on the core principle that powerful technology should be intuitive to use:
@route.*
and @widget.*
- that's all you need to learn"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.
Applications like insmind.com and gaoding.com demonstrate these benefits in practice:
💡 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🎯 Current Framework Support 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:
🏗️ Core Architecture: Simplicity in ActionWeb Widget's power comes from just two concepts - keeping it beautifully simple:
📄 Route Modules (*@route.*
)
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> ); });🧩 Widget Modules (
*@widget.*
)
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>🔀 Cross-Framework Magic: Power Unleashed
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> ); });⚡ Lightning Fast Performance
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 }
// 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()); });
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 RoutingWeb Widget supports file-system based routing conventions, automatically generating routemap.server.json
during development.
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> ); });Data Fetching and Processing
// ./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> ); }Vue Widget with Scoped Styles
<!-- ./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:
fetch
, Request
, Response
, Headers
, WebSocket
TextDecoder
, TextEncoder
, atob
, btoa
ReadableStream
, WritableStream
, TransformStream
crypto
, CryptoKey
, SubtleCrypto
AbortController
, URLPattern
, structuredClone
{ "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:
// 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
Server Actions: Seamless Client-Server IntegrationThe Web Platform Way: Instead of reinventing module sharing, we embrace the native solution that browsers are optimizing for.
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)); };
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>} </> ); }🌟 Why Server Actions Matter
HTTP Caching & PerformanceThe 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.
Web Widget provides enterprise-grade HTTP caching using standard Cache Control headers:
// 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, }, }, }; // ...
Project Setup & Configuration Complete Project StructureThis mode requires integrating the @web-widget/middlewares/cache middleware
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 toolsrenderStage="server"
for static content that doesn't need interactivityrenderStage="client"
for components that require browser APIsJoin developers exploring the future of multi-framework architecture.
🌟 The Future of Framework FreedomWeb 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.
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
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4