diff --git a/examples/with-koa/components/Edit.tsx b/examples/with-koa/components/Edit.tsx
new file mode 100644
index 0000000..69a41ea
--- /dev/null
+++ b/examples/with-koa/components/Edit.tsx
@@ -0,0 +1,7 @@
+export default function Edit({ name }: { name: string }) {
+ return (
+
+ Edit pages/{name}.tsx and save to test HMR
+
+ )
+}
\ No newline at end of file
diff --git a/examples/with-koa/package.json b/examples/with-koa/package.json
new file mode 100644
index 0000000..f7cff45
--- /dev/null
+++ b/examples/with-koa/package.json
@@ -0,0 +1,32 @@
+{
+ "type": "module",
+ "name": "reactus-with-koa",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "tsx scripts/develop.ts",
+ "build": "tsx scripts/build.ts",
+ "start": "tsx scripts/start.ts"
+ },
+ "dependencies": {
+ "koa": "^2.15.0",
+ "@koa/router": "^12.0.1",
+ "koa-static": "^5.0.0",
+ "react": "19.0.0",
+ "react-dom": "19.0.0",
+ "reactus": "0.2.10"
+ },
+ "devDependencies": {
+ "@types/koa": "^2.15.0",
+ "@types/koa__router": "^12.0.4",
+ "@types/koa-static": "^4.0.4",
+ "@types/node": "22.9.3",
+ "@types/react": "19.0.10",
+ "@types/react-dom": "19.0.4",
+ "@vitejs/plugin-react": "4.3.4",
+ "ts-node": "10.9.2",
+ "tsx": "4.19.3",
+ "typescript": "5.7.2",
+ "vite": "6.1.1"
+ }
+}
\ No newline at end of file
diff --git a/examples/with-koa/pages/about.tsx b/examples/with-koa/pages/about.tsx
new file mode 100644
index 0000000..969e59e
--- /dev/null
+++ b/examples/with-koa/pages/about.tsx
@@ -0,0 +1,28 @@
+import './page.css';
+import Edit from '../components/Edit';
+
+export function Head({ styles = [] }: { styles?: string[] }) {
+ return (
+ <>
+ About Reactus
+
+
+
+ {styles.map((href, index) => (
+
+ ))}
+ >
+ )
+}
+
+export default function AboutPage() {
+ return (
+ <>
+ About Reactus
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/examples/with-koa/pages/home.tsx b/examples/with-koa/pages/home.tsx
new file mode 100644
index 0000000..4710f9d
--- /dev/null
+++ b/examples/with-koa/pages/home.tsx
@@ -0,0 +1,34 @@
+import './page.css';
+import { useState } from 'react';
+import Edit from '../components/Edit';
+
+export function Head({ styles = [] }: { styles?: string[] }) {
+ return (
+ <>
+ Reactus
+
+
+
+ {styles.map((href, index) => (
+
+ ))}
+ >
+ )
+}
+
+export default function HomePage() {
+ const [count, setCount] = useState(0)
+
+ return (
+ <>
+ React + Reactus + Koa
+
+
+
+
About Reactus
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/examples/with-koa/pages/page.css b/examples/with-koa/pages/page.css
new file mode 100644
index 0000000..30e8b0c
--- /dev/null
+++ b/examples/with-koa/pages/page.css
@@ -0,0 +1,7 @@
+:root { display: initial; }
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
\ No newline at end of file
diff --git a/examples/with-koa/public/global.css b/examples/with-koa/public/global.css
new file mode 100644
index 0000000..2842e33
--- /dev/null
+++ b/examples/with-koa/public/global.css
@@ -0,0 +1,70 @@
+:root {
+ /*display: none; */
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-text-size-adjust: 100%;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
\ No newline at end of file
diff --git a/examples/with-koa/public/react.svg b/examples/with-koa/public/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/examples/with-koa/public/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/with-koa/scripts/build.ts b/examples/with-koa/scripts/build.ts
new file mode 100644
index 0000000..aa109d4
--- /dev/null
+++ b/examples/with-koa/scripts/build.ts
@@ -0,0 +1,41 @@
+//node
+import path from 'node:path';
+//reactus
+import { build } from 'reactus';
+
+async function builder() {
+ const cwd = process.cwd();
+ const engine = build({
+ cwd,
+ //path where to save assets (css, images, etc)
+ assetPath: path.join(cwd, 'public/assets'),
+ //path where to save and load (live) the client scripts (js)
+ clientPath: path.join(cwd, 'public/client'),
+ //path where to save and load (live) the server script (js)
+ pagePath: path.join(cwd, '.build/pages')
+ });
+
+ await engine.set('@/pages/home');
+ await engine.set('@/pages/about');
+
+ const responses = [
+ ...await engine.buildAllClients(),
+ ...await engine.buildAllAssets(),
+ ...await engine.buildAllPages()
+ ].map(response => {
+ const results = response.results;
+ if (typeof results?.contents === 'string') {
+ results.contents = results.contents.substring(0, 100) + ' ...';
+ }
+ return results;
+ });
+
+ //console.log(responses);
+ //fix for unused variable :)
+ if (responses.length) return;
+}
+
+builder().catch(e => {
+ console.error(e);
+ process.exit(1);
+});
\ No newline at end of file
diff --git a/examples/with-koa/scripts/develop.ts b/examples/with-koa/scripts/develop.ts
new file mode 100644
index 0000000..4fb5a4c
--- /dev/null
+++ b/examples/with-koa/scripts/develop.ts
@@ -0,0 +1,54 @@
+import { dev } from 'reactus';
+import Koa from 'koa';
+import Router from '@koa/router';
+
+
+async function develop() {
+ const engine = dev({
+ cwd: process.cwd(),
+ basePath: '/',
+ clientRoute: '/client',
+ })
+
+ const app = new Koa();
+ const router = new Router();
+
+ // Handle reactus http requests (public, assets, and hmr)
+ app.use(async (ctx, next) => {
+ await engine.http(ctx.req, ctx.res);
+
+ if (ctx.res.headersSent) return;
+ await next();
+ });
+
+ router.get('/', async (ctx) => {
+ ctx.set('Content-Type', 'text/html');
+ ctx.body = await engine.render('@/pages/home', { title: 'Home' });
+ return;
+ });
+
+ router.get('/about', async (ctx) => {
+ ctx.set('Content-Type', 'text/html');
+ ctx.body = await engine.render('@/pages/about');
+ return;
+ });
+
+ app.use(router.routes()).use(router.allowedMethods());
+
+ // Catch-all middleware for 404
+ app.use(async (ctx) => {
+ if (!ctx.body) {
+ ctx.status = 404;
+ ctx.body = '404 not found.';
+ }
+ });
+
+ app.listen(3000, () => {
+ console.log('Server listening at http://localhost:3000');
+ });
+}
+
+develop().catch(e => {
+ console.error(e);
+ process.exit(1);
+});
\ No newline at end of file
diff --git a/examples/with-koa/scripts/start.ts b/examples/with-koa/scripts/start.ts
new file mode 100644
index 0000000..f2d2437
--- /dev/null
+++ b/examples/with-koa/scripts/start.ts
@@ -0,0 +1,68 @@
+import path from 'node:path';
+import { serve } from 'reactus';
+import Koa from 'koa';
+import Router from '@koa/router';
+import koaStatic from 'koa-static';
+
+async function start() {
+ const app = new Koa();
+ const router = new Router();
+ const cwd = process.cwd();
+
+ const engine = serve({
+ cwd,
+ clientRoute: '/client',
+ pagePath: path.join(cwd, '.build/pages'),
+ cssRoute: '/assets'
+ });
+
+ const assets = koaStatic(path.join(cwd, 'public'), {
+ maxage: 31536000,
+ immutable: true,
+ });
+
+ // Middleware using koa-static to handle public assets
+ app.use(assets);
+
+ router.get('/', async (ctx) => {
+ try {
+ ctx.set('Content-Type', 'text/html');
+ ctx.body = await engine.render('@/pages/home');
+ } catch (error) {
+ console.error('Error rendering /home:', error);
+ ctx.status = 500;
+ ctx.body = 'Internal Server Error';
+ }
+ });
+
+ router.get('/about', async (ctx) => {
+ try {
+ ctx.set('Content-Type', 'text/html');
+ ctx.body = await engine.render('@/pages/about');
+ } catch (error) {
+ console.error('Error rendering /about:', error);
+ ctx.status = 500;
+ ctx.body = 'Internal Server Error';
+ }
+ });
+
+ app.use(router.routes()).use(router.allowedMethods());
+
+ // Catch-all middleware for 404
+ app.use(async (ctx) => {
+ if (!ctx.body) {
+ ctx.status = 404;
+ ctx.body = 'Not Found.';
+ }
+ });
+
+
+ app.listen(3000,() => {
+ console.log('Server listening at http://localhost:3000');
+ });
+}
+
+start().catch(e => {
+ console.error(e);
+ process.exit(1);
+});
\ No newline at end of file
diff --git a/examples/with-koa/tsconfig.json b/examples/with-koa/tsconfig.json
new file mode 100644
index 0000000..d6be85e
--- /dev/null
+++ b/examples/with-koa/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "reactus/tsconfig/client",
+ "compilerOptions": {
+ "moduleResolution": "bundler",
+ "outDir": ".build",
+ },
+ "include": [
+ "scripts/**/*.ts",
+ "components/**/*.tsx" ,
+ "pages/**/*.tsx"
+ ],
+ "exclude": [ "dist", "node_modules", "tests" ]
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 618c0b7..878db0e 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"fastify": "yarn --cwd examples/with-fastify",
"hapi": "yarn --cwd examples/with-hapi",
"http": "yarn --cwd examples/with-http",
+ "koa": "yarn --cwd examples/with-koa",
"nest": "yarn --cwd examples/with-nest",
"restify": "yarn --cwd examples/with-restify",
"tailwind": "yarn --cwd examples/with-tailwind",