diff --git a/e2e/fixtures/ssr-basic/src/main.tsx b/e2e/fixtures/ssr-basic/src/main.tsx
index bc436da93..fe026c6b2 100644
--- a/e2e/fixtures/ssr-basic/src/main.tsx
+++ b/e2e/fixtures/ssr-basic/src/main.tsx
@@ -10,7 +10,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/e2e/fixtures/ssr-swr/src/main.tsx b/e2e/fixtures/ssr-swr/src/main.tsx
index bc436da93..fe026c6b2 100644
--- a/e2e/fixtures/ssr-swr/src/main.tsx
+++ b/e2e/fixtures/ssr-swr/src/main.tsx
@@ -10,7 +10,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/e2e/fixtures/ssr-target-bundle/src/main.tsx b/e2e/fixtures/ssr-target-bundle/src/main.tsx
index bc436da93..fe026c6b2 100644
--- a/e2e/fixtures/ssr-target-bundle/src/main.tsx
+++ b/e2e/fixtures/ssr-target-bundle/src/main.tsx
@@ -10,7 +10,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/01_template/src/main.tsx b/examples/01_template/src/main.tsx
index 2ac42e6ae..a17518496 100644
--- a/examples/01_template/src/main.tsx
+++ b/examples/01_template/src/main.tsx
@@ -12,7 +12,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/02_demo/src/main.tsx b/examples/02_demo/src/main.tsx
index 2ac42e6ae..a17518496 100644
--- a/examples/02_demo/src/main.tsx
+++ b/examples/02_demo/src/main.tsx
@@ -12,7 +12,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/03_minimal/src/main.tsx b/examples/03_minimal/src/main.tsx
index bc436da93..fe026c6b2 100644
--- a/examples/03_minimal/src/main.tsx
+++ b/examples/03_minimal/src/main.tsx
@@ -10,7 +10,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/04_promise/src/main.tsx b/examples/04_promise/src/main.tsx
index dce443795..f213f62aa 100644
--- a/examples/04_promise/src/main.tsx
+++ b/examples/04_promise/src/main.tsx
@@ -12,7 +12,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/05_actions/src/main.tsx b/examples/05_actions/src/main.tsx
index bc436da93..fe026c6b2 100644
--- a/examples/05_actions/src/main.tsx
+++ b/examples/05_actions/src/main.tsx
@@ -10,7 +10,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/06_nesting/src/components/AppWithoutSsr.tsx b/examples/06_nesting/src/components/AppWithoutSsr.tsx
new file mode 100644
index 000000000..739bfd159
--- /dev/null
+++ b/examples/06_nesting/src/components/AppWithoutSsr.tsx
@@ -0,0 +1,14 @@
+import { CounterWithoutSsr } from './CounterWithoutSsr.js';
+
+const AppWithoutSsr = () => {
+ return (
+
+
Waku
+ Hello!!
+ This is a server component without SSR.
+
+
+ );
+};
+
+export default AppWithoutSsr;
diff --git a/examples/06_nesting/src/components/CounterWithoutSsr.tsx b/examples/06_nesting/src/components/CounterWithoutSsr.tsx
new file mode 100644
index 000000000..8691f10ce
--- /dev/null
+++ b/examples/06_nesting/src/components/CounterWithoutSsr.tsx
@@ -0,0 +1,20 @@
+'use client';
+
+import { useState } from 'react';
+
+export const CounterWithoutSsr = () => {
+ if (typeof window === 'undefined') {
+ throw new Error('This component is for client only.');
+ }
+ const [count, setCount] = useState(0);
+ const handleClick = () => {
+ setCount((c) => c + 1);
+ };
+ return (
+
+
Count: {count}
+
+
This is a client component.
+
+ );
+};
diff --git a/examples/06_nesting/src/entries.tsx b/examples/06_nesting/src/entries.tsx
index 8624b0612..0b0d31553 100644
--- a/examples/06_nesting/src/entries.tsx
+++ b/examples/06_nesting/src/entries.tsx
@@ -1,10 +1,10 @@
-import { lazy } from 'react';
import type { ReactNode } from 'react';
import { defineEntries } from 'waku/server';
import { Slot } from 'waku/client';
-const App = lazy(() => import('./components/App.js'));
-const InnerApp = lazy(() => import('./components/InnerApp.js'));
+import App from './components/App.js';
+import InnerApp from './components/InnerApp.js';
+import AppWithoutSsr from './components/AppWithoutSsr.js';
export default defineEntries(
// renderEntries
@@ -17,6 +17,9 @@ export default defineEntries(
if (params.has('InnerApp')) {
result.InnerApp = ;
}
+ if (params.has('AppWithoutSsr')) {
+ result.AppWithoutSsr = ;
+ }
return result;
},
// getBuildConfig
@@ -32,6 +35,11 @@ export default defineEntries(
{ input: 'InnerApp=5', skipPrefetch: true },
],
},
+ {
+ pathname: '/no-ssr',
+ entries: [{ input: 'AppWithoutSsr' }],
+ isStatic: true,
+ },
],
// getSsrConfig
async (pathname) => {
@@ -41,6 +49,8 @@ export default defineEntries(
input: '',
body: ,
};
+ case '/no-ssr':
+ return null;
default:
return null;
}
diff --git a/examples/06_nesting/src/main.tsx b/examples/06_nesting/src/main.tsx
index bc436da93..dc03c864f 100644
--- a/examples/06_nesting/src/main.tsx
+++ b/examples/06_nesting/src/main.tsx
@@ -2,15 +2,25 @@ import { StrictMode } from 'react';
import { createRoot, hydrateRoot } from 'react-dom/client';
import { Root, Slot } from 'waku/client';
+const pathname = window.location.pathname;
+
const rootElement = (
-
-
-
+ {pathname === '/' ? (
+
+
+
+ ) : pathname === '/no-ssr' ? (
+
+
+
+ ) : (
+ Not Found
+ )}
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/07_router/src/main.tsx b/examples/07_router/src/main.tsx
index 93b72ef52..271f927b7 100644
--- a/examples/07_router/src/main.tsx
+++ b/examples/07_router/src/main.tsx
@@ -12,7 +12,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/08_cookies/src/main.tsx b/examples/08_cookies/src/main.tsx
index bc436da93..fe026c6b2 100644
--- a/examples/08_cookies/src/main.tsx
+++ b/examples/08_cookies/src/main.tsx
@@ -10,7 +10,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/09_cssmodules/src/main.tsx b/examples/09_cssmodules/src/main.tsx
index bc436da93..fe026c6b2 100644
--- a/examples/09_cssmodules/src/main.tsx
+++ b/examples/09_cssmodules/src/main.tsx
@@ -10,7 +10,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/10_fs-router/src/main.tsx b/examples/10_fs-router/src/main.tsx
index 93b72ef52..271f927b7 100644
--- a/examples/10_fs-router/src/main.tsx
+++ b/examples/10_fs-router/src/main.tsx
@@ -12,7 +12,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/11_form/src/main.tsx b/examples/11_form/src/main.tsx
index bc436da93..fe026c6b2 100644
--- a/examples/11_form/src/main.tsx
+++ b/examples/11_form/src/main.tsx
@@ -10,7 +10,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/12_css/src/main.tsx b/examples/12_css/src/main.tsx
index bc436da93..fe026c6b2 100644
--- a/examples/12_css/src/main.tsx
+++ b/examples/12_css/src/main.tsx
@@ -10,7 +10,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/13_path-alias/src/main.tsx b/examples/13_path-alias/src/main.tsx
index bc436da93..fe026c6b2 100644
--- a/examples/13_path-alias/src/main.tsx
+++ b/examples/13_path-alias/src/main.tsx
@@ -10,7 +10,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/examples/14_react-tweet/src/main.tsx b/examples/14_react-tweet/src/main.tsx
index 2ac42e6ae..a17518496 100644
--- a/examples/14_react-tweet/src/main.tsx
+++ b/examples/14_react-tweet/src/main.tsx
@@ -12,7 +12,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);
diff --git a/packages/waku/src/cli.ts b/packages/waku/src/cli.ts
index 92a43f409..4a82c0cd1 100644
--- a/packages/waku/src/cli.ts
+++ b/packages/waku/src/cli.ts
@@ -98,14 +98,6 @@ async function runDev(options: { ssr: boolean }) {
'*',
honoDevMiddleware({ ...options, config, env: process.env as any }),
);
- app.notFound((c) => {
- // TODO Will re-consider this later. Should not be hard-coded.
- const file = 'public/404.html';
- if (existsSync(file)) {
- return c.html(readFileSync(file, 'utf8'), 404);
- }
- return c.text('404 Not Found', 404);
- });
const port = parseInt(process.env.PORT || '3000', 10);
await startServer(app, port);
}
diff --git a/packages/waku/src/lib/builder/build.ts b/packages/waku/src/lib/builder/build.ts
index 1b88cfe8f..5f5a03b89 100644
--- a/packages/waku/src/lib/builder/build.ts
+++ b/packages/waku/src/lib/builder/build.ts
@@ -296,7 +296,7 @@ const buildSsrBundle = async (
base: config.basePath,
plugins: [
rscIndexPlugin({ ...config, cssAssets }),
- rscEnvPlugin({ config, hydrate: true }),
+ rscEnvPlugin({ config }),
rscPrivatePlugin(config),
],
ssr: isNodeCompatible
@@ -349,7 +349,6 @@ const buildClientBundle = async (
config: ResolvedConfig,
clientEntryFiles: Record,
serverBuildOutput: Awaited>,
- ssr: boolean,
) => {
const mainJsFile = joinPath(rootDir, config.srcDir, config.mainJs);
const nonJsAssets = serverBuildOutput.output.flatMap(({ type, fileName }) =>
@@ -361,7 +360,7 @@ const buildClientBundle = async (
plugins: [
viteReact(),
rscIndexPlugin({ ...config, cssAssets }),
- rscEnvPlugin({ config, hydrate: ssr }),
+ rscEnvPlugin({ config }),
rscPrivatePlugin(config),
],
build: {
@@ -659,13 +658,7 @@ export async function build(options: {
isNodeCompatible,
);
}
- await buildClientBundle(
- rootDir,
- config,
- clientEntryFiles,
- serverBuildOutput,
- !!options.ssr,
- );
+ await buildClientBundle(rootDir, config, clientEntryFiles, serverBuildOutput);
const distEntries = await import(filePathToFileURL(distEntriesFile));
const buildConfig = await getBuildConfig({ config, entries: distEntries });
diff --git a/packages/waku/src/lib/handlers/handler-dev.ts b/packages/waku/src/lib/handlers/handler-dev.ts
index ad182016a..c84d2bfb9 100644
--- a/packages/waku/src/lib/handlers/handler-dev.ts
+++ b/packages/waku/src/lib/handlers/handler-dev.ts
@@ -56,7 +56,7 @@ export function createHandler<
base: config.basePath,
plugins: [
patchReactRefresh(viteReact()),
- rscEnvPlugin({ config, hydrate: ssr }),
+ rscEnvPlugin({ config }),
rscPrivatePlugin(config),
rscIndexPlugin(config),
rscHmrPlugin(),
@@ -129,16 +129,6 @@ export function createHandler<
});
};
- const willBeHandledByVite = async (pathname: string) => {
- const vite = await vitePromise;
- try {
- const result = await vite.transformRequest(pathname);
- return !!result;
- } catch {
- return false;
- }
- };
-
return async (req, res, next) => {
const [config, vite] = await Promise.all([configPromise, vitePromise]);
const basePrefix = config.basePath + config.rscPath + '/';
@@ -181,7 +171,7 @@ export function createHandler<
}
return;
}
- if (ssr && !(await willBeHandledByVite(req.url.pathname))) {
+ if (ssr) {
try {
const readable = await renderHtml({
config,
@@ -215,8 +205,6 @@ export function createHandler<
.pipeTo(res.stream);
return;
}
- next();
- return;
} catch (e) {
await handleError(e);
return;
diff --git a/packages/waku/src/lib/plugins/vite-plugin-rsc-env.ts b/packages/waku/src/lib/plugins/vite-plugin-rsc-env.ts
index ab394a1d3..325b60a81 100644
--- a/packages/waku/src/lib/plugins/vite-plugin-rsc-env.ts
+++ b/packages/waku/src/lib/plugins/vite-plugin-rsc-env.ts
@@ -2,13 +2,11 @@ import type { Plugin } from 'vite';
export function rscEnvPlugin({
config,
- hydrate,
}: {
config?: {
basePath: string;
rscPath: string;
};
- hydrate?: boolean | undefined;
}): Plugin {
return {
name: 'rsc-env-plugin',
@@ -28,9 +26,6 @@ export function rscEnvPlugin({
],
]
: []),
- ...(hydrate
- ? [['import.meta.env.WAKU_HYDRATE', JSON.stringify('true')]]
- : []),
...Object.entries((globalThis as any).__WAKU_PRIVATE_ENV__).flatMap(
([k, v]) =>
k.startsWith('WAKU_PUBLIC_')
diff --git a/packages/waku/src/lib/renderers/html-renderer.ts b/packages/waku/src/lib/renderers/html-renderer.ts
index ac5520ee9..fdcaffd79 100644
--- a/packages/waku/src/lib/renderers/html-renderer.ts
+++ b/packages/waku/src/lib/renderers/html-renderer.ts
@@ -97,7 +97,7 @@ globalThis.__WAKU_PREFETCHED__ = {
}
data += decoder.decode(chunk);
if (!headSent) {
- if (!data.includes('')) {
+ if (!/<\/head>]*>/.test(data)) {
return;
}
headSent = true;
@@ -148,7 +148,7 @@ const buildHtml = (
'html',
null,
createElement('head', { dangerouslySetInnerHTML: { __html: head } }),
- createElement('body', null, body),
+ createElement('body', { 'data-hydrate': true }, body),
);
export const renderHtml = async (
diff --git a/packages/website/src/main.tsx b/packages/website/src/main.tsx
index 2ac42e6ae..a17518496 100644
--- a/packages/website/src/main.tsx
+++ b/packages/website/src/main.tsx
@@ -12,7 +12,7 @@ const rootElement = (
);
-if (import.meta.env.WAKU_HYDRATE) {
+if (document.body.dataset.hydrate) {
hydrateRoot(document.body, rootElement);
} else {
createRoot(document.body).render(rootElement);