diff --git a/.github/ISSUE_TEMPLATE/blank.yml b/.github/ISSUE_TEMPLATE/blank.yml
new file mode 100644
index 00000000..1f71a477
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/blank.yml
@@ -0,0 +1,10 @@
+name: "Blank issue"
+description: Don't use this unless you are a Vike maintainer.
+body:
+ - type: markdown
+ attributes:
+ value: |
+ # **Don't use this** unless you are a Vike maintainer. Create a new issue at [Vike's main repository `github.com/vikejs/vike`](https://github.com/vikejs/vike/issues/new/choose) instead.
+ - type: textarea
+ attributes:
+ label: Description
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index a16fc79e..2d78da0f 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,11 +1,14 @@
-blank_issues_enabled: true
+blank_issues_enabled: false
contact_links:
- - name: "💥 Bug Report"
- url: https://github.com/vikejs/vike/issues/new/choose
- about: "Report bugs at Vike's main repository instead."
- - name: "✨ Feature Request"
- url: https://github.com/vikejs/vike/issues/new/choose
- about: "Suggest features at Vike's main repository instead."
+ - name: "💥 Bug"
+ url: https://github.com/vikejs/vike/issues/new?template=bug.yml
+ about: "Report a bug. (Redirects to Vike's main repository.)"
+ - name: "🚀 Feature"
+ url: https://github.com/vikejs/vike/issues/new?template=feature.yml
+ about: "Suggest a new feature. (Redirects to Vike's main repository.)"
+ - name: "✨ Polish"
+ url: https://github.com/vikejs/vike/issues/new?template=polish.yaml
+ about: "Unclear API or docs? Let us know — we'll polish Vike's DX. (Redirects to Vike's main repository.)"
- name: "🙏 Help & Questions"
url: https://github.com/vikejs/vike/discussions/new?category=help-questions
about: "Get official help from Vike maintainers."
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0f90b852..201410b1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -35,8 +35,8 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
- # TODO/eventually: try using the cache again
- # The cache breaks playwright https://github.com/vikejs/vike-vue/pull/119
+ # TO-DO/eventually: try using the cache again
+ # The cache breaks playwright installation, see https://github.com/vikejs/vike-vue/pull/119
# cache: "pnpm"
- run: pnpm install
diff --git a/.github/workflows/formatting.yml b/.github/workflows/format.yml
similarity index 80%
rename from .github/workflows/formatting.yml
rename to .github/workflows/format.yml
index d323c805..9993b22d 100644
--- a/.github/workflows/formatting.yml
+++ b/.github/workflows/format.yml
@@ -1,12 +1,11 @@
-name: 'Check formatting'
+name: Format
on:
push:
jobs:
- check_formatting:
+ main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- run: pnpm install
- run: pnpm run format:check
-
diff --git a/biome.json b/biome.json
index f81cc354..d2bfd4b4 100644
--- a/biome.json
+++ b/biome.json
@@ -16,10 +16,24 @@
}
},
"linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": false,
+ "style": {
+ "useNodejsImportProtocol": "error"
+ }
+ }
+ },
+ "organizeImports": {
"enabled": false
},
"vcs": {
"enabled": true,
"clientKind": "git"
+ },
+ "css": {
+ "formatter": {
+ "quoteStyle": "single"
+ }
}
}
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 00000000..2ef6bd74
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,2 @@
+> [!NOTE]
+> For more examples, see [Bati](https://batijs.dev) which generates `vike-react` apps.
diff --git a/examples/apollo/readme.md b/examples/apollo/README.md
similarity index 60%
rename from examples/apollo/readme.md
rename to examples/apollo/README.md
index 847f5dd6..910d856a 100644
--- a/examples/apollo/readme.md
+++ b/examples/apollo/README.md
@@ -1,5 +1,8 @@
Example of using `vike-react-apollo`.
+> [!NOTE]
+> For more examples, see [Bati](https://batijs.dev) which generates `vike-react` apps.
+
```bash
git clone git@github.com:vikejs/vike-react
cd vike-react/examples/apollo/
diff --git a/examples/apollo/package.json b/examples/apollo/package.json
index 5e93f2ba..6f7f76b7 100644
--- a/examples/apollo/package.json
+++ b/examples/apollo/package.json
@@ -1,23 +1,23 @@
{
"scripts": {
- "dev": "vite dev",
- "build": "vite build",
- "preview": "vite build && vite preview"
+ "dev": "vike dev",
+ "build": "vike build",
+ "preview": "vike build && vike preview"
},
"dependencies": {
- "@types/react": "^18.2.55",
- "@types/react-dom": "^18.2.19",
- "@vitejs/plugin-react": "^4.2.1",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "typescript": "^5.3.3",
- "vike": "^0.4.211",
- "vike-react": "^0.5.11",
- "vike-react-apollo": "^0.1.1",
+ "@types/react": "^19.1.13",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.3",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "typescript": "^5.9.2",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12",
+ "vike-react-apollo": "0.1.4",
"@apollo/client": "^3.10.8",
"@apollo/client-react-streaming": "^0.11.2",
"graphql": "^16.9.0",
- "vite": "^5.4.0"
+ "vite": "^7.1.7"
},
"type": "module"
}
diff --git a/examples/apollo/pages/+ApolloClient.ts b/examples/apollo/pages/+ApolloClient.ts
index 158f77fb..32b9cb6d 100644
--- a/examples/apollo/pages/+ApolloClient.ts
+++ b/examples/apollo/pages/+ApolloClient.ts
@@ -1,7 +1,7 @@
import { ApolloClient, InMemoryCache } from '@apollo/client-react-streaming'
import type { PageContext } from 'vike/types'
-// Apollo GraphQL Client with artificial delay: https://gist.github.com/brillout/7d7db0fd6ce55b3b5e8f7ec893eeda01
+// Same config but with artificial delay: https://gist.github.com/brillout/7d7db0fd6ce55b3b5e8f7ec893eeda01
export default (pageContext: PageContext) =>
new ApolloClient({
uri: 'https://countries.trevorblades.com',
diff --git a/examples/apollo/tsconfig.json b/examples/apollo/tsconfig.json
index e0bb64ac..6f6b091b 100644
--- a/examples/apollo/tsconfig.json
+++ b/examples/apollo/tsconfig.json
@@ -2,12 +2,18 @@
"compilerOptions": {
"strict": true,
"module": "ES2020",
- "moduleResolution": "Node",
+ "moduleResolution": "bundler",
"target": "ES2020",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"types": ["vite/client"],
"jsx": "react",
"skipLibCheck": true,
"esModuleInterop": true
- }
+ },
+ "include": [
+ "**/*",
+ // Include .test* files
+ // https://github.com/microsoft/TypeScript/issues/49555
+ "**/.*"
+ ]
}
diff --git a/examples/apollo/vite.config.ts b/examples/apollo/vite.config.ts
index aa36b02f..fd880aa9 100644
--- a/examples/apollo/vite.config.ts
+++ b/examples/apollo/vite.config.ts
@@ -4,4 +4,6 @@ import { UserConfig } from 'vite'
export default {
plugins: [react(), vike()],
+ // Seems like Apollo is heavy? Or is there a way to reduce the size of our Apollo imports?
+ build: { chunkSizeWarningLimit: 600 },
} satisfies UserConfig
diff --git a/examples/full/.testRun.ts b/examples/full/.testRun.ts
index c9237ff3..52c6421d 100644
--- a/examples/full/.testRun.ts
+++ b/examples/full/.testRun.ts
@@ -1,5 +1,5 @@
export { testRun }
-import { test, expect, run, fetchHtml, page, getServerUrl, autoRetry, partRegex } from '@brillout/test-e2e'
+import { test, expect, run, fetchHtml, page, getServerUrl, autoRetry, partRegex, expectLog } from '@brillout/test-e2e'
import assert from 'node:assert'
let isProd: boolean
@@ -43,6 +43,7 @@ function testRun(cmd: `pnpm run ${'dev' | 'preview'}`) {
testPageNavigation_betweenWithSSRAndWithout()
testPageNavigation_titleUpdate()
testUseConfig()
+ testReactSetting()
}
function testPageNavigation_betweenWithSSRAndWithout() {
@@ -68,21 +69,21 @@ function testPageNavigation_betweenWithSSRAndWithout() {
body = await page.textContent('body')
expect(body).toContain(t1)
expect(body).not.toContain(t2)
- ensureWasClientSideRouted('/pages/without-ssr')
+ await ensureWasClientSideRouted('/pages/without-ssr')
await page.click('a:has-text("Welcome")')
await testCounter()
body = await page.textContent('body')
expect(body).toContain(t2)
expect(body).not.toContain(t1)
- ensureWasClientSideRouted('/pages/without-ssr')
+ await ensureWasClientSideRouted('/pages/without-ssr')
await page.click('a:has-text("Without SSR")')
await testCounter()
body = await page.textContent('body')
expect(body).toContain(t1)
expect(body).not.toContain(t2)
- ensureWasClientSideRouted('/pages/without-ssr')
+ await ensureWasClientSideRouted('/pages/without-ssr')
})
}
@@ -205,10 +206,10 @@ function testUseConfig() {
test('useConfig() hydration', async () => {
await page.goto(getServerUrl() + '/')
await testCounter()
- ensureWasClientSideRouted('/pages/index')
+ await ensureWasClientSideRouted('/pages/index')
await page.click('a:has-text("useConfig()")')
await testCounter()
- ensureWasClientSideRouted('/pages/index')
+ await ensureWasClientSideRouted('/pages/index')
await page.goto(getServerUrl() + '/images')
await testCounter()
})
@@ -234,8 +235,9 @@ function findFirstPageId(html: string) {
expect(html.split('"pageId"').length).toBe(2)
const match = partRegex`"pageId":"${/([^"]+)/}"`.exec(html)
expect(match).toBeTruthy()
- const pageId = match![1]
+ let pageId = match![1]
expect(pageId).toBeTruthy()
+ pageId = pageId.replaceAll('\\\\/', '/')
return pageId
}
@@ -247,3 +249,12 @@ function getAssetUrl(fileName: string) {
assert(r.length === 0)
return partRegex`/assets/static/${fileBaseName}.${/[a-zA-Z0-9_-]+/}.${fileExt}`
}
+
+function testReactSetting() {
+ test('+react.{server.client}.js', async () => {
+ await page.goto(getServerUrl() + '/')
+ await testCounter()
+ expectLog('some-id-server-prefix', { filter: (log) => log.logSource === 'stdout' })
+ expectLog('some-id-client-prefix', { filter: (log) => log.logSource === 'Browser Log' })
+ })
+}
diff --git a/examples/full/README.md b/examples/full/README.md
new file mode 100644
index 00000000..d7ee1410
--- /dev/null
+++ b/examples/full/README.md
@@ -0,0 +1,11 @@
+Full-fledged example of using `vike-react`.
+
+> [!NOTE]
+> For more examples, see [Bati](https://batijs.dev) which generates `vike-react` apps.
+
+```bash
+git clone git@github.com:vikejs/vike-react
+cd vike-react/examples/full/
+npm install
+npm run dev
+```
diff --git a/examples/full/package.json b/examples/full/package.json
index fef6872a..fa1790ca 100644
--- a/examples/full/package.json
+++ b/examples/full/package.json
@@ -1,21 +1,21 @@
{
"scripts": {
- "dev": "vite dev",
- "build": "vite build",
- "preview": "vite build && vite preview"
+ "dev": "vike dev",
+ "build": "vike build",
+ "preview": "vike build && vike preview"
},
"dependencies": {
- "@types/react": "^18.3.3",
- "@types/react-dom": "^18.3.0",
- "@vitejs/plugin-react": "^4.3.1",
+ "@types/react": "^19.1.13",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.3",
"node-fetch": "^3.3.2",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "react-streaming": "^0.3.43",
- "typescript": "^5.5.4",
- "vike": "^0.4.211",
- "vike-react": "^0.5.11",
- "vite": "^5.4.0"
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "react-streaming": "^0.4.11",
+ "typescript": "^5.9.2",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12",
+ "vite": "^7.1.7"
},
"type": "module"
}
diff --git a/examples/full/pages/+Layout.tsx b/examples/full/pages/+Layout.tsx
index 277a28f2..91db1532 100644
--- a/examples/full/pages/+Layout.tsx
+++ b/examples/full/pages/+Layout.tsx
@@ -1,11 +1,19 @@
export { Layout }
import './style.css'
-import React from 'react'
+import React, { useEffect } from 'react'
import logoUrl from '../assets/logo.svg'
import { Link } from '../components/Link'
function Layout({ children }: { children: React.ReactNode }) {
+ /*
+ if (!import.meta.env.SSR) throw new Error('Some Failure')
+ //*/
+ useEffect(() => {
+ /*
+ throw new Error('Some Effect Failure')
+ //*/
+ })
return (
',
+ headHtmlEnd: '',
} satisfies Config
diff --git a/examples/full/pages/+react.client.ts b/examples/full/pages/+react.client.ts
new file mode 100644
index 00000000..e978863d
--- /dev/null
+++ b/examples/full/pages/+react.client.ts
@@ -0,0 +1,7 @@
+import type { Config } from 'vike/types'
+
+export default {
+ hydrateRootOptions: {
+ identifierPrefix: 'some-id-client-prefix',
+ },
+} satisfies Config['react']
diff --git a/examples/full/pages/+react.server.ts b/examples/full/pages/+react.server.ts
new file mode 100644
index 00000000..ac6a7168
--- /dev/null
+++ b/examples/full/pages/+react.server.ts
@@ -0,0 +1,8 @@
+import type { Config, PageContextServer } from 'vike/types'
+
+export default (_pageContext: PageContextServer) =>
+ ({
+ renderToStringOptions: {
+ identifierPrefix: 'some-id-server-prefix',
+ },
+ }) satisfies Config['react']
diff --git a/examples/full/pages/_error/+Page.tsx b/examples/full/pages/_error/+Page.tsx
index 2fa8b1cc..7225978a 100644
--- a/examples/full/pages/_error/+Page.tsx
+++ b/examples/full/pages/_error/+Page.tsx
@@ -8,14 +8,14 @@ function Page() {
if (is404) {
return (
<>
- 404 Page Not Found
+ Page Not Found
This page could not be found.
>
)
} else {
return (
<>
- 500 Internal Server Error
+ Internal Error
Something went wrong.
>
)
diff --git a/examples/full/pages/index/+Page.tsx b/examples/full/pages/index/+Page.tsx
index e909569b..476c85be 100644
--- a/examples/full/pages/index/+Page.tsx
+++ b/examples/full/pages/index/+Page.tsx
@@ -1,6 +1,6 @@
export default Page
-import React from 'react'
+import React, { useId } from 'react'
import { Counter } from '../../components/Counter'
import image from '../../assets/logo-new.svg'
import { Config } from 'vike-react/Config'
@@ -9,6 +9,9 @@ function Page() {
// Will be printed on the server and in the browser:
console.log('Rendering the landing page')
+ const id = useId()
+ console.log(id)
+
return (
<>
diff --git a/examples/full/pages/index/+config.ts b/examples/full/pages/index/+config.ts
new file mode 100644
index 00000000..e9174350
--- /dev/null
+++ b/examples/full/pages/index/+config.ts
@@ -0,0 +1,5 @@
+import type { Config } from 'vike/types'
+
+export default {
+ stream: false,
+} satisfies Config
diff --git a/examples/full/pages/starship/+Layout.tsx b/examples/full/pages/starship/+Layout.tsx
index d570d1be..609e646f 100644
--- a/examples/full/pages/starship/+Layout.tsx
+++ b/examples/full/pages/starship/+Layout.tsx
@@ -49,6 +49,7 @@ function Counter() {
function DummyText() {
return (
+ // spellcheck-ignore:on
<>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum
@@ -109,5 +110,6 @@ function DummyText() {
varius tincidunt libero. Phasellus dolor. Maecenas vestibulum mollis diam. Pellentesque ut neque.
>
+ // spellcheck-ignore:off
)
}
diff --git a/examples/full/pages/starship/+Page.tsx b/examples/full/pages/starship/+Page.tsx
index d0c121d1..9c549033 100644
--- a/examples/full/pages/starship/+Page.tsx
+++ b/examples/full/pages/starship/+Page.tsx
@@ -6,7 +6,7 @@ function Page() {
return (
<>
Overview
- The Starship will, at term, repalce all SpaceX's rocket models.
+ The Starship will, at term, replace all SpaceX's rocket models.
The mission: Make life multi planetary.
Starship drastically reduces the cost of sending payload to space, ensuring SpaceX's financial prosperity.
>
diff --git a/examples/full/readme.md b/examples/full/readme.md
deleted file mode 100644
index cd1eb558..00000000
--- a/examples/full/readme.md
+++ /dev/null
@@ -1,15 +0,0 @@
-Full-fledged example of using `vike-react`, showcasing:
-
-- [Layout](https://vike.dev/Layout)
-- Fetching data with [`data()`](https://vike.dev/data)
-- [Toggling SSR](https://vike.dev/ssr) on a per-page basis.
-- [HTML Streaming](https://vike.dev/streaming)
-- [Progressive Rendering](https://vike.dev/streaming#progressive-rendering)
-- [Error page](https://vike.dev/error-page)
-
-```bash
-git clone git@github.com:vikejs/vike-react
-cd vike-react/examples/full/
-npm install
-npm run dev
-```
diff --git a/examples/full/tsconfig.json b/examples/full/tsconfig.json
index e0bb64ac..6f6b091b 100644
--- a/examples/full/tsconfig.json
+++ b/examples/full/tsconfig.json
@@ -2,12 +2,18 @@
"compilerOptions": {
"strict": true,
"module": "ES2020",
- "moduleResolution": "Node",
+ "moduleResolution": "bundler",
"target": "ES2020",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"types": ["vite/client"],
"jsx": "react",
"skipLibCheck": true,
"esModuleInterop": true
- }
+ },
+ "include": [
+ "**/*",
+ // Include .test* files
+ // https://github.com/microsoft/TypeScript/issues/49555
+ "**/.*"
+ ]
}
diff --git a/examples/react-query/.test-dev.test.ts b/examples/minimal/.test-dev.test.ts
similarity index 100%
rename from examples/react-query/.test-dev.test.ts
rename to examples/minimal/.test-dev.test.ts
diff --git a/examples/react-query/.test-preview.test.ts b/examples/minimal/.test-preview.test.ts
similarity index 100%
rename from examples/react-query/.test-preview.test.ts
rename to examples/minimal/.test-preview.test.ts
diff --git a/examples/minimal/.testRun.ts b/examples/minimal/.testRun.ts
new file mode 100644
index 00000000..31c9c9b7
--- /dev/null
+++ b/examples/minimal/.testRun.ts
@@ -0,0 +1,49 @@
+export { testRunClassic as testRun }
+
+import { test, expect, run, fetchHtml, page, getServerUrl, autoRetry } from '@brillout/test-e2e'
+
+function testRunClassic(cmd: 'pnpm run dev' | 'pnpm run preview') {
+ run(cmd)
+
+ test('page content is rendered to HTML', async () => {
+ const html = await fetchHtml('/')
+ expect(html).toContain('Welcome
')
+ })
+
+ test('page is rendered to the DOM and interactive', async () => {
+ await page.goto(getServerUrl() + '/')
+ await page.click('a[href="/"]')
+ expect(await page.textContent('h1')).toBe('Welcome')
+ await testCounter()
+ })
+
+ test('about page', async () => {
+ await page.click('a[href="/about"]')
+ await autoRetry(async () => {
+ expect(await page.textContent('h1')).toBe('About')
+ })
+ expect(await page.textContent('p')).toBe('Example of using Vike.')
+ const html = await fetchHtml('/about')
+ expect(html).toContain('About
')
+ })
+}
+
+async function testCounter(currentValue = 0) {
+ // autoRetry() in case page just got client-side navigated
+ await autoRetry(
+ async () => {
+ const btn = page.locator('button', { hasText: 'Counter' })
+ expect(await btn.textContent()).toBe(`Counter ${currentValue}`)
+ },
+ { timeout: 5 * 1000 },
+ )
+ // autoRetry() in case page isn't hydrated yet
+ await autoRetry(
+ async () => {
+ const btn = page.locator('button', { hasText: 'Counter' })
+ await btn.click()
+ expect(await btn.textContent()).toBe(`Counter ${currentValue + 1}`)
+ },
+ { timeout: 5 * 1000 },
+ )
+}
diff --git a/examples/minimal/readme.md b/examples/minimal/README.md
similarity index 60%
rename from examples/minimal/readme.md
rename to examples/minimal/README.md
index 3ea764e7..7c2cfc3c 100644
--- a/examples/minimal/readme.md
+++ b/examples/minimal/README.md
@@ -1,5 +1,8 @@
Minimal example of using `vike-react`.
+> [!NOTE]
+> For more examples, see [Bati](https://batijs.dev) which generates `vike-react` apps.
+
```bash
git clone git@github.com:vikejs/vike-react
cd vike-react/examples/minimal/
diff --git a/examples/minimal/package.json b/examples/minimal/package.json
index bc524dba..b733e285 100644
--- a/examples/minimal/package.json
+++ b/examples/minimal/package.json
@@ -1,16 +1,16 @@
{
"scripts": {
- "dev": "vite dev",
- "build": "vite build",
- "preview": "vite build && vite preview"
+ "dev": "vike dev",
+ "build": "vike build",
+ "preview": "vike build && vike preview"
},
"dependencies": {
- "@vitejs/plugin-react": "^4.2.1",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "vike": "^0.4.203",
- "vike-react": "^0.5.11",
- "vite": "^5.4.0"
+ "@vitejs/plugin-react": "^5.0.3",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12",
+ "vite": "^7.1.7"
},
"type": "module"
}
diff --git a/examples/react-query/.gitignore b/examples/query/.gitignore
similarity index 100%
rename from examples/react-query/.gitignore
rename to examples/query/.gitignore
diff --git a/examples/query/.test-dev.test.ts b/examples/query/.test-dev.test.ts
new file mode 100644
index 00000000..fbafdbbc
--- /dev/null
+++ b/examples/query/.test-dev.test.ts
@@ -0,0 +1,2 @@
+import { testRun } from './.testRun'
+testRun('pnpm run dev')
diff --git a/examples/query/.test-preview.test.ts b/examples/query/.test-preview.test.ts
new file mode 100644
index 00000000..6cd5bbe4
--- /dev/null
+++ b/examples/query/.test-preview.test.ts
@@ -0,0 +1,2 @@
+import { testRun } from './.testRun'
+testRun('pnpm run preview')
diff --git a/examples/react-query/.testRun.ts b/examples/query/.testRun.ts
similarity index 79%
rename from examples/react-query/.testRun.ts
rename to examples/query/.testRun.ts
index ae8182e6..f2e3870d 100644
--- a/examples/react-query/.testRun.ts
+++ b/examples/query/.testRun.ts
@@ -31,11 +31,24 @@ function testRun(cmd: `pnpm run ${'dev' | 'preview'}`) {
})
test('DOM', async () => {
await page.goto(getServerUrl() + '/')
- const body = await page.textContent('body')
- // Playwright seems to await the HTML stream
- expect(body).not.toContain(loading)
- expect(body).toContain(content)
+ const getBody = async () => await page.textContent('body')
+ const isLoading = async () => {
+ const body = await getBody()
+ expect(body).toContain(loading)
+ /* Playwright seems to await the HTML stream?
+ expect(body).not.toContain(content)
+ */
+ }
+ const isLoaded = async () => {
+ const body = await getBody()
+ expect(body).toContain(content)
+ expect(body).not.toContain(loading)
+ }
+ await isLoading()
await testCounter()
+ await isLoading()
+ // expect(await getBody()).not.toContain(content)
+ await autoRetry(isLoaded)
})
}
diff --git a/examples/query/README.md b/examples/query/README.md
new file mode 100644
index 00000000..beab345b
--- /dev/null
+++ b/examples/query/README.md
@@ -0,0 +1,11 @@
+Example of using `vike-react-query`.
+
+> [!NOTE]
+> For more examples, see [Bati](https://batijs.dev) which generates `vike-react` apps.
+
+```bash
+git clone git@github.com:vikejs/vike-react
+cd vike-react/examples/query/
+npm install
+npm run dev
+```
diff --git a/examples/react-query/assets/logo.svg b/examples/query/assets/logo.svg
similarity index 100%
rename from examples/react-query/assets/logo.svg
rename to examples/query/assets/logo.svg
diff --git a/examples/react-query/layouts/HeadDefault.tsx b/examples/query/layouts/HeadDefault.tsx
similarity index 100%
rename from examples/react-query/layouts/HeadDefault.tsx
rename to examples/query/layouts/HeadDefault.tsx
diff --git a/examples/react-query/layouts/LayoutDefault.tsx b/examples/query/layouts/LayoutDefault.tsx
similarity index 100%
rename from examples/react-query/layouts/LayoutDefault.tsx
rename to examples/query/layouts/LayoutDefault.tsx
diff --git a/examples/react-query/layouts/style.css b/examples/query/layouts/style.css
similarity index 100%
rename from examples/react-query/layouts/style.css
rename to examples/query/layouts/style.css
diff --git a/examples/query/package.json b/examples/query/package.json
new file mode 100644
index 00000000..feb55811
--- /dev/null
+++ b/examples/query/package.json
@@ -0,0 +1,20 @@
+{
+ "scripts": {
+ "dev": "vike dev",
+ "preview": "vike build && vike preview"
+ },
+ "dependencies": {
+ "@types/react": "^19.1.13",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.3",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "typescript": "^5.9.2",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12",
+ "vike-react-query": "0.1.10",
+ "@tanstack/react-query": "^5.20.1",
+ "vite": "^7.1.7"
+ },
+ "type": "module"
+}
diff --git a/examples/react-query/pages/+config.ts b/examples/query/pages/+config.ts
similarity index 100%
rename from examples/react-query/pages/+config.ts
rename to examples/query/pages/+config.ts
diff --git a/examples/react-query/pages/index/+Page.tsx b/examples/query/pages/index/+Page.tsx
similarity index 100%
rename from examples/react-query/pages/index/+Page.tsx
rename to examples/query/pages/index/+Page.tsx
diff --git a/examples/react-query/pages/index/@id/+Page.tsx b/examples/query/pages/index/@id/+Page.tsx
similarity index 100%
rename from examples/react-query/pages/index/@id/+Page.tsx
rename to examples/query/pages/index/@id/+Page.tsx
diff --git a/examples/react-query/pages/index/@id/Movie.tsx b/examples/query/pages/index/@id/Movie.tsx
similarity index 100%
rename from examples/react-query/pages/index/@id/Movie.tsx
rename to examples/query/pages/index/@id/Movie.tsx
diff --git a/examples/react-query/pages/index/Counter.tsx b/examples/query/pages/index/Counter.tsx
similarity index 100%
rename from examples/react-query/pages/index/Counter.tsx
rename to examples/query/pages/index/Counter.tsx
diff --git a/examples/react-query/pages/index/Movies.tsx b/examples/query/pages/index/Movies.tsx
similarity index 100%
rename from examples/react-query/pages/index/Movies.tsx
rename to examples/query/pages/index/Movies.tsx
diff --git a/examples/react-query/pages/index/types.ts b/examples/query/pages/index/types.ts
similarity index 100%
rename from examples/react-query/pages/index/types.ts
rename to examples/query/pages/index/types.ts
diff --git a/examples/react-query/tsconfig.json b/examples/query/tsconfig.json
similarity index 58%
rename from examples/react-query/tsconfig.json
rename to examples/query/tsconfig.json
index e0bb64ac..6f6b091b 100644
--- a/examples/react-query/tsconfig.json
+++ b/examples/query/tsconfig.json
@@ -2,12 +2,18 @@
"compilerOptions": {
"strict": true,
"module": "ES2020",
- "moduleResolution": "Node",
+ "moduleResolution": "bundler",
"target": "ES2020",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"types": ["vite/client"],
"jsx": "react",
"skipLibCheck": true,
"esModuleInterop": true
- }
+ },
+ "include": [
+ "**/*",
+ // Include .test* files
+ // https://github.com/microsoft/TypeScript/issues/49555
+ "**/.*"
+ ]
}
diff --git a/examples/react-query/vite.config.ts b/examples/query/vite.config.ts
similarity index 100%
rename from examples/react-query/vite.config.ts
rename to examples/query/vite.config.ts
diff --git a/examples/react-query/package.json b/examples/react-query/package.json
deleted file mode 100644
index a43b8c89..00000000
--- a/examples/react-query/package.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "scripts": {
- "dev": "vite dev",
- "preview": "vite build && vite preview"
- },
- "dependencies": {
- "@types/react": "^18.2.55",
- "@types/react-dom": "^18.2.19",
- "@vitejs/plugin-react": "^4.2.1",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "typescript": "^5.5.3",
- "vike": "^0.4.211",
- "vike-react": "^0.5.11",
- "vike-react-query": "^0.1.3",
- "@tanstack/react-query": "^5.20.1",
- "vite": "^5.4.0"
- },
- "type": "module"
-}
diff --git a/examples/redux/.gitignore b/examples/redux/.gitignore
new file mode 100644
index 00000000..b0a5c349
--- /dev/null
+++ b/examples/redux/.gitignore
@@ -0,0 +1,2 @@
+/node_modules/
+/dist/
diff --git a/examples/redux/.test-dev.test.ts b/examples/redux/.test-dev.test.ts
new file mode 100644
index 00000000..fbafdbbc
--- /dev/null
+++ b/examples/redux/.test-dev.test.ts
@@ -0,0 +1,2 @@
+import { testRun } from './.testRun'
+testRun('pnpm run dev')
diff --git a/examples/redux/.test-preview-ssg.test.ts b/examples/redux/.test-preview-ssg.test.ts
new file mode 100644
index 00000000..4ba2ae61
--- /dev/null
+++ b/examples/redux/.test-preview-ssg.test.ts
@@ -0,0 +1,2 @@
+import { testRun } from './.testRun'
+testRun('pnpm run preview:ssg')
diff --git a/examples/redux/.test-preview.test.ts b/examples/redux/.test-preview.test.ts
new file mode 100644
index 00000000..6cd5bbe4
--- /dev/null
+++ b/examples/redux/.test-preview.test.ts
@@ -0,0 +1,2 @@
+import { testRun } from './.testRun'
+testRun('pnpm run preview')
diff --git a/examples/redux/.testRun.ts b/examples/redux/.testRun.ts
new file mode 100644
index 00000000..286f556e
--- /dev/null
+++ b/examples/redux/.testRun.ts
@@ -0,0 +1,98 @@
+export { testRun }
+
+import { test, expect, run, page, getServerUrl, autoRetry, fetchHtml, sleep } from '@brillout/test-e2e'
+
+function testRun(cmd: `pnpm run ${'dev' | 'preview' | 'preview:ssg'}`) {
+ const isDev = cmd === 'pnpm run dev'
+
+ run(cmd)
+
+ test('count', async () => {
+ await page.goto(getServerUrl() + '/')
+ await testCounter()
+ await clientSideNavigation()
+ await fullPageReload()
+ })
+ async function clientSideNavigation() {
+ await page.click('a:has-text("About")')
+ await page.waitForFunction(() => (window as any)._vike.fullyRenderedUrl === '/about')
+ await testCounter(1)
+ await page.click('a:has-text("Welcome")')
+ await page.waitForFunction(() => (window as any)._vike.fullyRenderedUrl === '/')
+ await testCounter(2)
+ }
+ async function fullPageReload() {
+ await page.goto(getServerUrl() + '/about')
+ await testCounter()
+ await page.goto(getServerUrl() + '/')
+ await testCounter()
+ }
+
+ test('todos - initial list', async () => {
+ await page.goto(getServerUrl() + '/')
+ await expectInitialList()
+ })
+ async function expectInitialList() {
+ const buyApples = 'Buy apples'
+ const nodeVersion = `Node.js ${process.version}`
+ {
+ const html = await fetchHtml('/')
+ expect(html).toContain(`${buyApples}`)
+ expect(html).toContain(nodeVersion)
+ }
+ {
+ const bodyText = await page.textContent('body')
+ expect(bodyText).toContain(buyApples)
+ expect(bodyText).toContain(nodeVersion)
+ expect(await getNumberOfItems()).toBe(2)
+ }
+ }
+
+ test('todos - add to-do', async () => {
+ expect(await getNumberOfItems()).toBe(2)
+ if (isDev) await sleep(300) // Seems to be required, otherwise the test is flaky. I don't know why.
+ await page.fill('input[type="text"]', 'Buy bananas')
+ await page.click('button[type="submit"]')
+ const expectBananas = async () => {
+ await autoRetry(async () => {
+ expect(await getNumberOfItems()).toBe(3)
+ })
+ expect(await page.textContent('body')).toContain('Buy bananas')
+ }
+ await expectBananas()
+
+ await testCounter()
+ await clientSideNavigation()
+ await expectBananas()
+
+ // Full page reload
+ await fullPageReload()
+ await expectInitialList()
+ })
+}
+
+async function getNumberOfItems() {
+ return await page.evaluate(() => document.querySelectorAll('#todo-list li').length)
+}
+
+async function testCounter(inc: 0 | 1 | 2 = 0) {
+ const counterInitValue = 42
+ const currentValue = counterInitValue + inc
+ // autoRetry() in case page just got client-side navigated
+ await autoRetry(
+ async () => {
+ const btn = page.locator('button', { hasText: 'Counter' })
+ expect(await btn.textContent()).toBe(`Counter ${currentValue}`)
+ },
+ { timeout: 5 * 1000 },
+ )
+ // autoRetry() in case page isn't hydrated yet
+ await autoRetry(
+ async () => {
+ const btn = page.locator('button', { hasText: 'Counter' })
+ await btn.click()
+ expect(await btn.textContent()).toBe(`Counter ${currentValue + 1}`)
+ },
+ { timeout: 5 * 1000 },
+ )
+}
diff --git a/examples/react-query/readme.md b/examples/redux/README.md
similarity index 52%
rename from examples/react-query/readme.md
rename to examples/redux/README.md
index 7163a375..cfcd6f09 100644
--- a/examples/react-query/readme.md
+++ b/examples/redux/README.md
@@ -1,8 +1,8 @@
-Example of using `vike-react-query`.
+Example of using `vike-react-redux`.
```bash
git clone git@github.com:vikejs/vike-react
-cd vike-react/examples/react-query/
+cd vike-react/examples/redux/
npm install
npm run dev
```
diff --git a/examples/redux/components/Counter.tsx b/examples/redux/components/Counter.tsx
new file mode 100644
index 00000000..f91e89e2
--- /dev/null
+++ b/examples/redux/components/Counter.tsx
@@ -0,0 +1,15 @@
+export { Counter }
+
+import React from 'react'
+import { useAppDispatch, useAppSelector } from '../store/hooks'
+import { increment, selectCount } from '../store/slices/count'
+
+function Counter() {
+ const dispatch = useAppDispatch()
+ const count = useAppSelector(selectCount)
+ return (
+
+ )
+}
diff --git a/examples/redux/components/Counter/fetchCountInit.ts b/examples/redux/components/Counter/fetchCountInit.ts
new file mode 100644
index 00000000..3be04b0c
--- /dev/null
+++ b/examples/redux/components/Counter/fetchCountInit.ts
@@ -0,0 +1,6 @@
+export { fetchCountInit }
+
+// Pretending the value is fetched over the network
+async function fetchCountInit() {
+ return 42
+}
diff --git a/examples/redux/components/Link.tsx b/examples/redux/components/Link.tsx
new file mode 100644
index 00000000..3bb18237
--- /dev/null
+++ b/examples/redux/components/Link.tsx
@@ -0,0 +1,15 @@
+export { Link }
+
+import { usePageContext } from 'vike-react/usePageContext'
+import React from 'react'
+
+function Link({ href, children }: { href: string; children: string }) {
+ const pageContext = usePageContext()
+ const { urlPathname } = pageContext
+ const isActive = href === '/' ? urlPathname === href : urlPathname.startsWith(href)
+ return (
+
+ {children}
+
+ )
+}
diff --git a/examples/redux/layouts/LayoutDefault.tsx b/examples/redux/layouts/LayoutDefault.tsx
new file mode 100644
index 00000000..3e3acd35
--- /dev/null
+++ b/examples/redux/layouts/LayoutDefault.tsx
@@ -0,0 +1,75 @@
+export default LayoutDefault
+
+import './style.css'
+import React from 'react'
+import logoUrl from './logo.svg'
+import { Link } from '../components/Link'
+
+function LayoutDefault({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+ Welcome
+ About
+
+ {children}
+
+ )
+}
+
+function Sidebar({ children }: { children: React.ReactNode }) {
+ return (
+
+ )
+}
+
+function Content({ children }: { children: React.ReactNode }) {
+ return (
+
+ )
+}
+
+function Logo() {
+ return (
+
+ )
+}
diff --git a/examples/redux/layouts/logo.svg b/examples/redux/layouts/logo.svg
new file mode 100644
index 00000000..94d3caa0
--- /dev/null
+++ b/examples/redux/layouts/logo.svg
@@ -0,0 +1,36 @@
+
+
diff --git a/examples/redux/layouts/style.css b/examples/redux/layouts/style.css
new file mode 100644
index 00000000..7afa4ca5
--- /dev/null
+++ b/examples/redux/layouts/style.css
@@ -0,0 +1,29 @@
+/* Links */
+a {
+ text-decoration: none;
+}
+#sidebar a {
+ padding: 2px 10px;
+ margin-left: -10px;
+}
+#sidebar a.is-active {
+ background-color: #eee;
+}
+
+/* Reset */
+body {
+ margin: 0;
+ font-family: sans-serif;
+}
+* {
+ box-sizing: border-box;
+}
+
+/* Page Transition Anmiation */
+#page-content {
+ opacity: 1;
+ transition: opacity 0.3s ease-in-out;
+}
+body.page-is-transitioning #page-content {
+ opacity: 0;
+}
diff --git a/examples/redux/package.json b/examples/redux/package.json
new file mode 100644
index 00000000..edd69521
--- /dev/null
+++ b/examples/redux/package.json
@@ -0,0 +1,23 @@
+{
+ "scripts": {
+ "dev": "vike dev",
+ "build": "vike build",
+ "preview": "vike build && vike preview",
+ "preview:ssg": "vike build --prerender && vike preview --prerender"
+ },
+ "dependencies": {
+ "@reduxjs/toolkit": "^2.8.2",
+ "@types/react": "^19.1.13",
+ "@types/react-dom": "^19.1.9",
+ "@vitejs/plugin-react": "^5.0.3",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "react-redux": "^9.2.0",
+ "typescript": "^5.9.2",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12",
+ "vike-react-redux": "0.1.1",
+ "vite": "^7.1.7"
+ },
+ "type": "module"
+}
diff --git a/examples/redux/pages/+config.ts b/examples/redux/pages/+config.ts
new file mode 100644
index 00000000..3dfc4810
--- /dev/null
+++ b/examples/redux/pages/+config.ts
@@ -0,0 +1,9 @@
+import Layout from '../layouts/LayoutDefault'
+import vikeReact from 'vike-react/config'
+import vikeReactRedux from 'vike-react-redux/config'
+import type { Config } from 'vike/types'
+
+export default {
+ Layout,
+ extends: [vikeReact, vikeReactRedux],
+} satisfies Config
diff --git a/examples/redux/pages/+redux.ts b/examples/redux/pages/+redux.ts
new file mode 100644
index 00000000..ef74551b
--- /dev/null
+++ b/examples/redux/pages/+redux.ts
@@ -0,0 +1,2 @@
+import { createStore } from '../store/createStore'
+export default { createStore }
diff --git a/examples/redux/pages/about/+Page.tsx b/examples/redux/pages/about/+Page.tsx
new file mode 100644
index 00000000..e970b685
--- /dev/null
+++ b/examples/redux/pages/about/+Page.tsx
@@ -0,0 +1,12 @@
+import React from 'react'
+import { Counter } from '../../components/Counter'
+
+export default function Page() {
+ return (
+ <>
+ About
+ The counter value is the same as on the Welcome page.
+
+ >
+ )
+}
diff --git a/examples/redux/pages/about/+config.ts b/examples/redux/pages/about/+config.ts
new file mode 100644
index 00000000..c39550d8
--- /dev/null
+++ b/examples/redux/pages/about/+config.ts
@@ -0,0 +1,5 @@
+import type { Config } from 'vike/types'
+
+export default {
+ ssr: false,
+} satisfies Config
diff --git a/examples/redux/pages/about/+data.ts b/examples/redux/pages/about/+data.ts
new file mode 100644
index 00000000..62295fa1
--- /dev/null
+++ b/examples/redux/pages/about/+data.ts
@@ -0,0 +1,13 @@
+// Environment: server
+export { data }
+export type Data = Awaited>
+
+import { fetchCountInit } from '../../components/Counter/fetchCountInit'
+import type { PageContextServer } from 'vike/types'
+
+async function data(pageContext: PageContextServer) {
+ const countInitial = await fetchCountInit()
+ return {
+ countInitial,
+ }
+}
diff --git a/examples/redux/pages/about/+onData.ts b/examples/redux/pages/about/+onData.ts
new file mode 100644
index 00000000..6776cc6b
--- /dev/null
+++ b/examples/redux/pages/about/+onData.ts
@@ -0,0 +1,16 @@
+// Environment: server, client
+export { onData }
+
+import type { PageContext } from 'vike/types'
+import type { Data } from './+data'
+import { initializeCount } from '../../store/slices/count'
+
+function onData(pageContext: PageContext & { data?: Data }) {
+ const { store } = pageContext
+ store.dispatch(initializeCount(pageContext.data!.countInitial))
+
+ // Saving KBs: we don't need pageContext.data (we use the store instead)
+ // - If we don't delete pageContext.data then Vike sends pageContext.data to the client-side
+ // - This optimization only works if the page is SSR'd: if the page is pre-rendered then don't do this
+ if (!pageContext.isPrerendering) delete pageContext.data
+}
diff --git a/examples/redux/pages/index/+Page.tsx b/examples/redux/pages/index/+Page.tsx
new file mode 100644
index 00000000..347ab341
--- /dev/null
+++ b/examples/redux/pages/index/+Page.tsx
@@ -0,0 +1,19 @@
+import React from 'react'
+import { Counter } from '../../components/Counter'
+import { TodoList } from './TodoList'
+
+export default function Page() {
+ return (
+ <>
+ My Vike app
+ This page is:
+
+ - Rendered to HTML.
+ -
+ Interactive.
+
+
+
+ >
+ )
+}
diff --git a/examples/redux/pages/index/+data.ts b/examples/redux/pages/index/+data.ts
new file mode 100644
index 00000000..7cff33d9
--- /dev/null
+++ b/examples/redux/pages/index/+data.ts
@@ -0,0 +1,20 @@
+// Environment: server
+export { data }
+export type Data = Awaited>
+
+import { fetchCountInit } from '../../components/Counter/fetchCountInit'
+import type { PageContextServer } from 'vike/types'
+
+async function data(pageContext: PageContextServer) {
+ const [countInitial, todoItemsInitial] = await Promise.all([fetchCountInit(), fetchTodosInit()])
+ return { countInitial, todoItemsInitial }
+}
+
+// Pretending the list is fetched over the network
+async function fetchTodosInit() {
+ return [
+ //
+ { text: 'Buy apples' },
+ { text: `Update Node.js ${process.version} to latest version` },
+ ]
+}
diff --git a/examples/redux/pages/index/+onData.ts b/examples/redux/pages/index/+onData.ts
new file mode 100644
index 00000000..bd833fe5
--- /dev/null
+++ b/examples/redux/pages/index/+onData.ts
@@ -0,0 +1,18 @@
+// Environment: server, client
+export { onData }
+
+import type { PageContext } from 'vike/types'
+import type { Data } from './+data'
+import { initializeCount } from '../../store/slices/count'
+import { initializeTodos } from '../../store/slices/todos'
+
+function onData(pageContext: PageContext & { data?: Data }) {
+ const { store } = pageContext
+ store.dispatch(initializeTodos(pageContext.data!.todoItemsInitial))
+ store.dispatch(initializeCount(pageContext.data!.countInitial))
+
+ // Saving KBs: we don't need pageContext.data (we use the store instead)
+ // - If we don't delete pageContext.data then Vike sends pageContext.data to the client-side
+ // - This optimization only works if the page is SSR'd: if the page is pre-rendered then don't do this
+ if (!pageContext.isPrerendering) delete pageContext.data
+}
diff --git a/examples/redux/pages/index/TodoList.tsx b/examples/redux/pages/index/TodoList.tsx
new file mode 100644
index 00000000..02d2f484
--- /dev/null
+++ b/examples/redux/pages/index/TodoList.tsx
@@ -0,0 +1,34 @@
+import { useState } from 'react'
+
+import React from 'react'
+import { useAppDispatch, useAppSelector } from '../../store/hooks'
+import { addTodo, selectTodos } from '../../store/slices/todos'
+
+export function TodoList() {
+ const [newTodo, setNewTodo] = useState('')
+ const dispatch = useAppDispatch()
+ const todoItems = useAppSelector(selectTodos)
+ return (
+ <>
+ To-Do
+
+ {todoItems.map((todoItem, index) => (
+ // biome-ignore:
+ - {todoItem.text}
+ ))}
+
+
+
+
+ >
+ )
+}
diff --git a/examples/redux/store/createStore.ts b/examples/redux/store/createStore.ts
new file mode 100644
index 00000000..a1644a73
--- /dev/null
+++ b/examples/redux/store/createStore.ts
@@ -0,0 +1,15 @@
+export { createStore }
+export type AppStore = ReturnType
+export type RootState = ReturnType
+export type AppDispatch = AppStore['dispatch']
+
+import type { PageContext } from 'vike/types'
+import { combineReducers, configureStore } from '@reduxjs/toolkit'
+import { countReducer } from './slices/count'
+import { todosReducer } from './slices/todos'
+const reducer = combineReducers({ count: countReducer, todos: todosReducer })
+
+function createStore(pageContext: PageContext) {
+ const preloadedState = pageContext.isClientSide ? pageContext.redux?.ssrState : undefined
+ return configureStore({ reducer, preloadedState })
+}
diff --git a/examples/redux/store/hooks.ts b/examples/redux/store/hooks.ts
new file mode 100644
index 00000000..bb99df62
--- /dev/null
+++ b/examples/redux/store/hooks.ts
@@ -0,0 +1,8 @@
+// This file serves as a central hub for re-exporting pre-typed Redux hooks.
+import { useDispatch, useSelector, useStore } from 'react-redux'
+import type { AppDispatch, AppStore, RootState } from './createStore'
+
+// Use throughout your app instead of plain `useDispatch` and `useSelector`
+export const useAppDispatch = useDispatch.withTypes()
+export const useAppSelector = useSelector.withTypes()
+export const useAppStore = useStore.withTypes()
diff --git a/examples/redux/store/slices/count.ts b/examples/redux/store/slices/count.ts
new file mode 100644
index 00000000..933bf814
--- /dev/null
+++ b/examples/redux/store/slices/count.ts
@@ -0,0 +1,28 @@
+import { createSlice } from '@reduxjs/toolkit'
+import type { PayloadAction } from '@reduxjs/toolkit'
+
+const initialState = { countValue: 0 }
+
+const countSlice = createSlice({
+ name: 'count',
+ initialState,
+ reducers: {
+ increment: (state) => {
+ state.countValue += 1
+ },
+ decrement: (state) => {
+ state.countValue -= 1
+ },
+ initializeCount: (state, action: PayloadAction) => {
+ if (state.countValue !== 0) return
+ state.countValue = action.payload
+ },
+ },
+ selectors: {
+ selectCount: (state) => state.countValue,
+ },
+})
+
+export const countReducer = countSlice.reducer
+export const { selectCount } = countSlice.selectors
+export const { increment, decrement, initializeCount } = countSlice.actions
diff --git a/examples/redux/store/slices/todos.ts b/examples/redux/store/slices/todos.ts
new file mode 100644
index 00000000..3f90741a
--- /dev/null
+++ b/examples/redux/store/slices/todos.ts
@@ -0,0 +1,26 @@
+import { createSlice } from '@reduxjs/toolkit'
+import type { PayloadAction } from '@reduxjs/toolkit'
+
+type Todo = { text: string }
+const initialState = { todoItems: [] as Todo[] }
+
+const todosSlice = createSlice({
+ name: 'todos',
+ initialState,
+ reducers: {
+ addTodo: (state, action: PayloadAction) => {
+ state.todoItems.push({ text: action.payload })
+ },
+ initializeTodos: (state, action: PayloadAction) => {
+ if (state.todoItems.length > 0) return
+ state.todoItems = action.payload
+ },
+ },
+ selectors: {
+ selectTodos: (state) => state.todoItems,
+ },
+})
+
+export const todosReducer = todosSlice.reducer
+export const { selectTodos } = todosSlice.selectors
+export const { addTodo, initializeTodos } = todosSlice.actions
diff --git a/examples/redux/tsconfig.json b/examples/redux/tsconfig.json
new file mode 100644
index 00000000..6f6b091b
--- /dev/null
+++ b/examples/redux/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "module": "ES2020",
+ "moduleResolution": "bundler",
+ "target": "ES2020",
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "types": ["vite/client"],
+ "jsx": "react",
+ "skipLibCheck": true,
+ "esModuleInterop": true
+ },
+ "include": [
+ "**/*",
+ // Include .test* files
+ // https://github.com/microsoft/TypeScript/issues/49555
+ "**/.*"
+ ]
+}
diff --git a/examples/redux/vite.config.ts b/examples/redux/vite.config.ts
new file mode 100644
index 00000000..aa36b02f
--- /dev/null
+++ b/examples/redux/vite.config.ts
@@ -0,0 +1,7 @@
+import react from '@vitejs/plugin-react'
+import vike from 'vike/plugin'
+import { UserConfig } from 'vite'
+
+export default {
+ plugins: [react(), vike()],
+} satisfies UserConfig
diff --git a/examples/zustand/.gitignore b/examples/zustand/.gitignore
new file mode 100644
index 00000000..b0a5c349
--- /dev/null
+++ b/examples/zustand/.gitignore
@@ -0,0 +1,2 @@
+/node_modules/
+/dist/
diff --git a/examples/zustand/.test-dev.test.ts b/examples/zustand/.test-dev.test.ts
new file mode 100644
index 00000000..fbafdbbc
--- /dev/null
+++ b/examples/zustand/.test-dev.test.ts
@@ -0,0 +1,2 @@
+import { testRun } from './.testRun'
+testRun('pnpm run dev')
diff --git a/examples/zustand/.test-preview.test.ts b/examples/zustand/.test-preview.test.ts
new file mode 100644
index 00000000..6cd5bbe4
--- /dev/null
+++ b/examples/zustand/.test-preview.test.ts
@@ -0,0 +1,2 @@
+import { testRun } from './.testRun'
+testRun('pnpm run preview')
diff --git a/examples/zustand/.testRun.ts b/examples/zustand/.testRun.ts
new file mode 100644
index 00000000..8aa83b12
--- /dev/null
+++ b/examples/zustand/.testRun.ts
@@ -0,0 +1,129 @@
+export { testRun }
+
+import {
+ test,
+ expect,
+ run,
+ fetchHtml,
+ page,
+ getServerUrl,
+ autoRetry,
+ partRegex,
+ expectLog,
+ sleep,
+} from '@brillout/test-e2e'
+
+function testRun(cmd: 'pnpm run dev' | 'pnpm run preview') {
+ const isDev = cmd === 'pnpm run dev'
+
+ run(cmd)
+
+ test('page content is rendered to HTML', async () => {
+ const html = await fetchHtml('/')
+ expect(html).toContain('Welcome
')
+ })
+
+ test('page is rendered to the DOM and interactive', async () => {
+ await page.goto(getServerUrl() + '/')
+ expect(await page.textContent('h1')).toBe('Welcome')
+ await testCounter()
+ })
+
+ test('store is persisted upon client-side navigation', async () => {
+ await page.goto(getServerUrl() + '/')
+ let value = await testCounter()
+ await page.click('a:has-text("About")')
+ await page.waitForFunction(() => (window as any)._vike.fullyRenderedUrl === '/about')
+ await testCounter(value)
+ value++
+ await page.click('a:has-text("Welcome")')
+ await page.waitForFunction(() => (window as any)._vike.fullyRenderedUrl === '/')
+ await testCounter(value)
+ value++
+ })
+
+ test('todos - initial list', async () => {
+ await page.goto(getServerUrl() + '/')
+ await expectInitialList()
+ })
+ async function expectInitialList() {
+ const buyApples = 'Buy apples'
+ const nodeVersion = `Node.js ${process.version}`
+ {
+ const html = await fetchHtml('/')
+ expect(html).toContain(`${buyApples}`)
+ expect(html).toContain(nodeVersion)
+ }
+ {
+ const bodyText = await page.textContent('body')
+ expect(bodyText).toContain(buyApples)
+ expect(bodyText).toContain(nodeVersion)
+ expect(await getNumberOfItems()).toBe(2)
+ }
+ }
+
+ test('todos - add to-do', async () => {
+ expect(await getNumberOfItems()).toBe(2)
+ if (isDev) await sleep(300) // Seems to be required, otherwise the test is flaky. I don't know why.
+ await page.fill('input[type="text"]', 'Buy bananas')
+ await page.click('button[type="submit"]')
+ const expectBananas = async () => {
+ await autoRetry(async () => {
+ expect(await getNumberOfItems()).toBe(3)
+ expect(await page.textContent('body')).toContain('Buy bananas')
+ })
+ }
+ await expectBananas()
+ expectLog('{"text":"Buy bananas"}') // See `storeVanilla.subscribe()`
+
+ await clientSideNavigation()
+ await expectBananas()
+
+ // Full page reload
+ await fullPageReload()
+ await expectInitialList()
+ })
+ async function clientSideNavigation() {
+ await page.click('a:has-text("About")')
+ await page.waitForFunction(() => (window as any)._vike.fullyRenderedUrl === '/about')
+ await page.click('a:has-text("Welcome")')
+ await page.waitForFunction(() => (window as any)._vike.fullyRenderedUrl === '/')
+ }
+ async function fullPageReload() {
+ await page.goto(getServerUrl() + '/about')
+ await page.goto(getServerUrl() + '/')
+ }
+}
+
+async function getNumberOfItems() {
+ return await page.evaluate(() => document.querySelectorAll('#todo-list li').length)
+}
+
+async function testCounter(currentValue?: number) {
+ // autoRetry() in case page just got client-side navigated
+ await autoRetry(
+ async () => {
+ const btn = page.locator('button', { hasText: 'Counter' })
+ const content = await btn.textContent()
+ expect(content).toMatch(partRegex`Counter ${/[0-9]+/}`)
+ const value = parseInt(content!.slice('Counter '.length), 10)
+ if (currentValue) {
+ expect(value).toBe(currentValue)
+ } else {
+ currentValue = value
+ }
+ },
+ { timeout: 5 * 1000 },
+ )
+ const valueNew = currentValue! + 1
+ // autoRetry() in case page isn't hydrated yet
+ await autoRetry(
+ async () => {
+ const btn = page.locator('button', { hasText: 'Counter' })
+ await btn.click()
+ expect(await btn.textContent()).toBe(`Counter ${valueNew}`)
+ },
+ { timeout: 5 * 1000 },
+ )
+ return valueNew
+}
diff --git a/examples/zustand/README.md b/examples/zustand/README.md
new file mode 100644
index 00000000..533d0470
--- /dev/null
+++ b/examples/zustand/README.md
@@ -0,0 +1,8 @@
+Example of using `vike-react-zustand`.
+
+```bash
+git clone git@github.com:vikejs/vike-react
+cd vike-react/examples/zustand/
+npm install
+npm run dev
+```
diff --git a/examples/zustand/assets/logo.svg b/examples/zustand/assets/logo.svg
new file mode 100644
index 00000000..94d3caa0
--- /dev/null
+++ b/examples/zustand/assets/logo.svg
@@ -0,0 +1,36 @@
+
+
diff --git a/examples/zustand/components/Counter.tsx b/examples/zustand/components/Counter.tsx
new file mode 100644
index 00000000..15da217b
--- /dev/null
+++ b/examples/zustand/components/Counter.tsx
@@ -0,0 +1,10 @@
+export { Counter }
+
+import React from 'react'
+import { useCounterStore } from '../store'
+
+function Counter() {
+ const { counter, setCounter } = useCounterStore()
+
+ return
+}
diff --git a/examples/zustand/layouts/HeadDefault.tsx b/examples/zustand/layouts/HeadDefault.tsx
new file mode 100644
index 00000000..3de2fc65
--- /dev/null
+++ b/examples/zustand/layouts/HeadDefault.tsx
@@ -0,0 +1,11 @@
+export default HeadDefault
+
+import React from 'react'
+
+function HeadDefault() {
+ return (
+ <>
+
+ >
+ )
+}
diff --git a/examples/zustand/layouts/LayoutDefault.tsx b/examples/zustand/layouts/LayoutDefault.tsx
new file mode 100644
index 00000000..a856414a
--- /dev/null
+++ b/examples/zustand/layouts/LayoutDefault.tsx
@@ -0,0 +1,72 @@
+export default LayoutDefault
+
+import './style.css'
+import React from 'react'
+import logoUrl from '../assets/logo.svg'
+
+function LayoutDefault({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+ {children}
+
+ )
+}
+
+function Sidebar({ children }: { children: React.ReactNode }) {
+ return (
+
+ )
+}
+
+function Content({ children }: { children: React.ReactNode }) {
+ return (
+
+ )
+}
+
+function Logo() {
+ return (
+
+ )
+}
diff --git a/examples/zustand/layouts/style.css b/examples/zustand/layouts/style.css
new file mode 100644
index 00000000..7afa4ca5
--- /dev/null
+++ b/examples/zustand/layouts/style.css
@@ -0,0 +1,29 @@
+/* Links */
+a {
+ text-decoration: none;
+}
+#sidebar a {
+ padding: 2px 10px;
+ margin-left: -10px;
+}
+#sidebar a.is-active {
+ background-color: #eee;
+}
+
+/* Reset */
+body {
+ margin: 0;
+ font-family: sans-serif;
+}
+* {
+ box-sizing: border-box;
+}
+
+/* Page Transition Anmiation */
+#page-content {
+ opacity: 1;
+ transition: opacity 0.3s ease-in-out;
+}
+body.page-is-transitioning #page-content {
+ opacity: 0;
+}
diff --git a/examples/zustand/package.json b/examples/zustand/package.json
new file mode 100644
index 00000000..2cc6781c
--- /dev/null
+++ b/examples/zustand/package.json
@@ -0,0 +1,23 @@
+{
+ "scripts": {
+ "dev": "vite dev",
+ "build": "vite build",
+ "preview": "vite build && vite preview",
+ "test": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@types/react": "^19.2.2",
+ "@types/react-dom": "^19.2.1",
+ "@vitejs/plugin-react": "^5.0.4",
+ "immer": "^10.1.3",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "typescript": "^5.9.3",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12",
+ "vike-react-zustand": "0.1.5",
+ "vite": "^7.1.9",
+ "zustand": "^5.0.8"
+ },
+ "type": "module"
+}
diff --git a/examples/zustand/pages/+Layout.tsx b/examples/zustand/pages/+Layout.tsx
new file mode 100644
index 00000000..2f77156e
--- /dev/null
+++ b/examples/zustand/pages/+Layout.tsx
@@ -0,0 +1,12 @@
+import React from 'react'
+
+export const Layout = ({ children }: { children: React.ReactNode }) => {
+ return (
+
+
+ {children}
+
+ )
+}
diff --git a/examples/zustand/pages/+config.ts b/examples/zustand/pages/+config.ts
new file mode 100644
index 00000000..66056d70
--- /dev/null
+++ b/examples/zustand/pages/+config.ts
@@ -0,0 +1,10 @@
+export { config }
+
+import type { Config } from 'vike/types'
+import vikeReact from 'vike-react/config'
+import vikeReactZustand from 'vike-react-zustand/config'
+
+const config = {
+ title: 'My Vike + React App',
+ extends: [vikeReact, vikeReactZustand],
+} satisfies Config
diff --git a/examples/zustand/pages/_error/+Page.tsx b/examples/zustand/pages/_error/+Page.tsx
new file mode 100644
index 00000000..131ecc07
--- /dev/null
+++ b/examples/zustand/pages/_error/+Page.tsx
@@ -0,0 +1,22 @@
+export default Page
+
+import React from 'react'
+
+function Page({ is404, errorInfo }: { is404: boolean; errorInfo?: string }) {
+ if (is404) {
+ return (
+ <>
+ 404 Page Not Found
+ This page could not be found.
+ {errorInfo}
+ >
+ )
+ } else {
+ return (
+ <>
+ 500 Internal Server Error
+ Something went wrong.
+ >
+ )
+ }
+}
diff --git a/examples/zustand/pages/about/+Page.tsx b/examples/zustand/pages/about/+Page.tsx
new file mode 100644
index 00000000..bc1cee36
--- /dev/null
+++ b/examples/zustand/pages/about/+Page.tsx
@@ -0,0 +1,16 @@
+export default Page
+
+import React from 'react'
+import { Counter } from '../../components/Counter'
+
+function Page() {
+ return (
+ <>
+ <>
+ About
+ The counter value is the same as on the Welcome page.
+
+ >
+ >
+ )
+}
diff --git a/examples/zustand/pages/index/+Page.tsx b/examples/zustand/pages/index/+Page.tsx
new file mode 100644
index 00000000..1680c1a9
--- /dev/null
+++ b/examples/zustand/pages/index/+Page.tsx
@@ -0,0 +1,21 @@
+export { Page }
+
+import React from 'react'
+import { Counter } from '../../components/Counter'
+import { TodoList } from './TodoList'
+
+function Page() {
+ return (
+ <>
+ Welcome
+ This page is:
+
+ - Rendered to HTML.
+ -
+ Interactive while loading.
+
+
+
+ >
+ )
+}
diff --git a/examples/zustand/pages/index/+data.ts b/examples/zustand/pages/index/+data.ts
new file mode 100644
index 00000000..d95ec61e
--- /dev/null
+++ b/examples/zustand/pages/index/+data.ts
@@ -0,0 +1,19 @@
+// Environment: server
+export { data }
+export type Data = Awaited>
+
+import type { PageContextServer } from 'vike/types'
+
+async function data(pageContext: PageContextServer) {
+ const todoItemsInitial = await fetchTodosInit()
+ return { todoItemsInitial }
+}
+
+// Pretending the list is fetched over the network
+async function fetchTodosInit() {
+ return [
+ //
+ { text: 'Buy apples' },
+ { text: `Update Node.js ${process.version} to latest version` },
+ ]
+}
diff --git a/examples/zustand/pages/index/TodoList.tsx b/examples/zustand/pages/index/TodoList.tsx
new file mode 100644
index 00000000..156864f7
--- /dev/null
+++ b/examples/zustand/pages/index/TodoList.tsx
@@ -0,0 +1,42 @@
+import { useEffect, useState } from 'react'
+
+import React from 'react'
+import { useTodoStore } from '../../store'
+import { useStoreVanilla } from 'vike-react-zustand'
+
+export function TodoList() {
+ const [newTodo, setNewTodo] = useState('')
+ const { todoItems, addTodo } = useTodoStore()
+ const storeVanilla = useStoreVanilla(useTodoStore)
+ useEffect(
+ () =>
+ storeVanilla.subscribe((state) => {
+ console.log(JSON.stringify(state.todoItems))
+ }),
+ [],
+ )
+
+ return (
+ <>
+ To-Do
+
+ {todoItems.map((todoItem, index) => (
+ // biome-ignore:
+ - {todoItem.text}
+ ))}
+
+
+
+
+ >
+ )
+}
diff --git a/examples/zustand/store.ts b/examples/zustand/store.ts
new file mode 100644
index 00000000..4f105f9b
--- /dev/null
+++ b/examples/zustand/store.ts
@@ -0,0 +1,39 @@
+export { useCounterStore }
+export { useTodoStore }
+
+import { create, withPageContext } from 'vike-react-zustand'
+import { immer } from 'zustand/middleware/immer'
+import type { Data } from './pages/index/+data'
+
+interface CounterStore {
+ counter: number
+ setCounter: (value: number) => void
+}
+const useCounterStore = create()(
+ immer((set, get) => ({
+ setCounter(value) {
+ set((state) => {
+ state.counter = value
+ })
+ },
+ counter: Math.floor(10000 * Math.random()),
+ })),
+)
+
+type Todo = { text: string }
+interface TodoStore {
+ todoItems: Todo[]
+ addTodo: (todo: Todo) => void
+}
+const useTodoStore = create()(
+ withPageContext((pageContext) =>
+ immer((set, get) => ({
+ todoItems: (pageContext.data as Data).todoItemsInitial,
+ addTodo(todo) {
+ set((state) => {
+ state.todoItems.push(todo)
+ })
+ },
+ })),
+ ),
+)
diff --git a/examples/zustand/tsconfig.json b/examples/zustand/tsconfig.json
new file mode 100644
index 00000000..6f6b091b
--- /dev/null
+++ b/examples/zustand/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "module": "ES2020",
+ "moduleResolution": "bundler",
+ "target": "ES2020",
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "types": ["vite/client"],
+ "jsx": "react",
+ "skipLibCheck": true,
+ "esModuleInterop": true
+ },
+ "include": [
+ "**/*",
+ // Include .test* files
+ // https://github.com/microsoft/TypeScript/issues/49555
+ "**/.*"
+ ]
+}
diff --git a/examples/zustand/vite.config.ts b/examples/zustand/vite.config.ts
new file mode 100644
index 00000000..03bb667e
--- /dev/null
+++ b/examples/zustand/vite.config.ts
@@ -0,0 +1,7 @@
+import react from '@vitejs/plugin-react'
+import vike from 'vike/plugin'
+import type { UserConfig } from 'vite'
+
+export default {
+ plugins: [react(), vike()],
+} satisfies UserConfig
diff --git a/package.json b/package.json
index 76b31c8a..cbf6dba0 100644
--- a/package.json
+++ b/package.json
@@ -1,42 +1,32 @@
{
"scripts": {
"========= Build": "",
- "build": "pnpm --recursive --filter {packages/*} run build",
+ "build": "pnpm --recursive --filter \"{packages/*}\" run build",
"========= Dev": "",
"dev": "cd ./packages/vike-react/ && pnpm run dev",
"========= Test": "",
"test": "pnpm run test:units && pnpm run test:e2e && pnpm run test:types",
"test:e2e": "test-e2e",
- "test:units": "pnpm --recursive --sequential --filter {packages/*} run test",
+ "test:units": "pnpm --recursive --sequential run test:units",
"test:types": "test-types",
"========= Formatting": "",
"format": "pnpm run format:biome",
"format:prettier": "git ls-files | egrep '\\.(json|js|jsx|css|ts|tsx|vue|mjs|cjs)$' | grep --invert-match package.json | xargs pnpm exec prettier --write",
- "format:biome": "biome format --write .",
- "format:check": "biome format . || (echo 'Fix formatting by running `$ pnpm run -w format`.' && exit 1)",
+ "format:biome": "biome check --write --unsafe",
+ "format:check": "biome ci || (echo '\\033[1;34mFix errors by running `$ pnpm run -w format`.\\033[0m' && exit 1)",
"========= Release": "",
"release": "cd ./packages/vike-react/ && pnpm run release",
"release:minor": "cd ./packages/vike-react/ && pnpm run release:minor",
"release:commit": "cd ./packages/vike-react/ && pnpm run release:commit",
+ "release:all": "pnpm --recursive --sequential run release --yes",
"========= Reset": "",
"reset": "git clean -Xdf && pnpm install && pnpm run build",
"========= Only allow pnpm; forbid yarn & npm": "",
"preinstall": "npx only-allow pnpm"
},
- "pnpm": {
- "overrides": {
- "vike-react": "link:./packages/vike-react/",
- "vike-react-query": "link:./packages/vike-react-query/",
- "vike-react-apollo": "link:./packages/vike-react-apollo/",
- "vike-react-chakra": "link:./packages/vike-react-chakra/",
- "vike-react-antd": "link:./packages/vike-react-antd/",
- "vike-react-styled-components": "link:./packages/vike-react-styled-components/",
- "vike-react-styled-jsx": "link:./packages/vike-react-styled-jsx/"
- }
- },
"devDependencies": {
- "@biomejs/biome": "^1.8.3",
- "@brillout/test-e2e": "^0.5.33",
+ "@biomejs/biome": "^1.9.4",
+ "@brillout/test-e2e": "^0.6.16",
"@brillout/test-types": "^0.1.15",
"playwright": "^1.45.0",
"prettier": "^3.2.5"
diff --git a/packages/vike-react-antd/CHANGELOG.md b/packages/vike-react-antd/CHANGELOG.md
index 4d328c34..505fbeb8 100644
--- a/packages/vike-react-antd/CHANGELOG.md
+++ b/packages/vike-react-antd/CHANGELOG.md
@@ -1,3 +1,12 @@
+## [1.0.3](https://github.com/vikejs/vike-react/compare/vike-react-antd@1.0.2...vike-react-antd@1.0.3) (2025-07-01)
+
+
+### Bug Fixes
+
+* fix repo link on npm ([7a85501](https://github.com/vikejs/vike-react/commit/7a85501148774c871a342881cbe9f06678378754))
+
+
+
## [1.0.2](https://github.com/vikejs/vike-react/compare/vike-react-antd@1.0.1...vike-react-antd@1.0.2) (2024-12-28)
diff --git a/packages/vike-react-antd/README.md b/packages/vike-react-antd/README.md
index 81a99548..401c139c 100644
--- a/packages/vike-react-antd/README.md
+++ b/packages/vike-react-antd/README.md
@@ -1,12 +1,16 @@
+
+
+[](https://www.npmjs.com/package/vike-react-antd)
+
# `vike-react-antd`
-Integrates [Ant Design](https://ant.design) to your [`vike-react`](https://vike.dev/vike-react) app.
+Integrates [Ant Design](https://ant.design) into your [`vike-react`](https://vike.dev/vike-react) app.
[Installation](#installation)
[Settings](#settings)
[Version history](https://github.com/vikejs/vike-react/blob/main/packages/vike-react-antd/CHANGELOG.md)
[What it does](#what-it-does)
-[See Also](#see-also)
+[See also](#see-also)
@@ -25,7 +29,7 @@ Integrates [Ant Design](https://ant.design) to your [`vike-react`](https://vike.
extends: [vikeReact, vikeReactAntd]
}
```
-3. You can now use Ant Design at any of your components.
+3. You can now use Ant Design in any of your components.
```jsx
import { Button, Flex } from "antd";
diff --git a/packages/vike-react-antd/package.json b/packages/vike-react-antd/package.json
index 6571b6ab..a82c4150 100644
--- a/packages/vike-react-antd/package.json
+++ b/packages/vike-react-antd/package.json
@@ -1,6 +1,7 @@
{
"name": "vike-react-antd",
- "version": "1.0.2",
+ "version": "1.0.3",
+ "homepage": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-antd#readme",
"type": "module",
"exports": {
"./config": "./dist/config.js",
@@ -24,15 +25,15 @@
},
"devDependencies": {
"@ant-design/cssinjs": "^1.22.1",
- "@brillout/release-me": "^0.4.2",
- "@types/react": "^18.2.55",
+ "@brillout/release-me": "^0.4.8",
+ "@types/react": "^19.1.13",
"antd": "^5.22.5",
- "react": "^18.3.1",
+ "react": "^19.2.0",
"rimraf": "^5.0.5",
- "typescript": "^5.5.3",
- "vike": "^0.4.211",
- "vike-react": "^0.5.11",
- "vite": "^5.4.0"
+ "typescript": "^5.9.2",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12",
+ "vite": "^7.1.7"
},
"typesVersions": {
"*": {
@@ -53,6 +54,5 @@
"files": [
"dist"
],
- "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-antd",
"license": "MIT"
}
diff --git a/packages/vike-react-apollo/CHANGELOG.md b/packages/vike-react-apollo/CHANGELOG.md
index f3b5f000..08f275cd 100644
--- a/packages/vike-react-apollo/CHANGELOG.md
+++ b/packages/vike-react-apollo/CHANGELOG.md
@@ -1,3 +1,30 @@
+## [0.1.4](https://github.com/vikejs/vike-react/compare/vike-react-apollo@0.1.3...vike-react-apollo@0.1.4) (2025-09-16)
+
+
+### Bug Fixes
+
+* react-streaming@^0.4.4 ([6420a27](https://github.com/vikejs/vike-react/commit/6420a277e86d0cf829de21f2a22fcf070f1075cd))
+
+
+
+## [0.1.3](https://github.com/vikejs/vike-react/compare/vike-react-apollo@0.1.2...vike-react-apollo@0.1.3) (2025-07-01)
+
+
+### Bug Fixes
+
+* fix repo link on npm ([7a85501](https://github.com/vikejs/vike-react/commit/7a85501148774c871a342881cbe9f06678378754))
+
+
+
+## [0.1.2](https://github.com/vikejs/vike-react/compare/vike-react-apollo@0.1.1...vike-react-apollo@0.1.2) (2025-05-29)
+
+
+### Bug Fixes
+
+* update +stream usage ([#175](https://github.com/vikejs/vike-react/issues/175)) ([7a3d1d6](https://github.com/vikejs/vike-react/commit/7a3d1d601f0ff2ff45409d92b3226f544eaf24c7))
+
+
+
## [0.1.1](https://github.com/vikejs/vike-react/compare/vike-react-apollo@0.1.0...vike-react-apollo@0.1.1) (2024-08-05)
diff --git a/packages/vike-react-apollo/README.md b/packages/vike-react-apollo/README.md
index b5406145..ab7e0200 100644
--- a/packages/vike-react-apollo/README.md
+++ b/packages/vike-react-apollo/README.md
@@ -4,27 +4,32 @@
# `vike-react-apollo`
-Enables your React components to fetch data using [Apollo GraphQL](https://www.apollographql.com). Powered by [HTML streaming](https://github.com/brillout/react-streaming#readme).
+Enables your React components to fetch data using [Apollo GraphQL](https://www.apollographql.com).
-> [!NOTE]
-> Includes:
-> - [Progressive rendering](https://vike.dev/streaming#progressive-rendering)
-> - [SSR benefits](https://github.com/brillout/react-streaming#ssr)
-> - Fallback upon loading and/or error
-> - [Caching](https://www.apollographql.com/docs/react/caching/cache-configuration)
+Powered by [`react-streaming`](https://github.com/brillout/react-streaming#readme).
+
+Features:
+- [Progressive Rendering](https://vike.dev/streaming#progressive-rendering)
+- [SSR benefits](https://github.com/brillout/react-streaming#ssr)
+- Fallback upon loading and/or error
+- [Caching](https://www.apollographql.com/docs/react/caching/cache-configuration)
+
+
+
+**Table of Contents**
[Installation](#installation)
[Basic usage](#basic-usage)
+[Example](#example)
[`withFallback()`](#withfallback)
[`` tags](#head-tags)
[Error Handling](#error-handling)
[How it works](#how-it-works)
-[Version history](https://github.com/vikejs/vike-react/blob/main/packages/vike-react-apollo/CHANGELOG.md)
+[Version history](#version-history)
[See also](#see-also)
-
## Installation
1. `npm install @apollo/client @apollo/client-react-streaming graphql vike-react-apollo`
@@ -46,7 +51,7 @@ Enables your React components to fetch data using [Apollo GraphQL](https://www.a
import { ApolloClient, InMemoryCache } from '@apollo/client-react-streaming'
- export default (pageContext: PageContext) =>
+ export default (pageContext) =>
new ApolloClient({
uri: 'https://countries.trevorblades.com',
cache: new InMemoryCache()
@@ -58,7 +63,6 @@ Enables your React components to fetch data using [Apollo GraphQL](https://www.a
-
## Basic usage
```jsx
@@ -89,8 +93,20 @@ const Countries = () => {
> [!NOTE]
> Even though [`useSuspenseQuery()`](https://www.apollographql.com/docs/react/api/react/hooks/#usesuspensequery) is imported from `@apollo/client`, you need to install `vike-react-apollo` for it to work. (The `useSuspenseQuery()` hook requires an [HTML stream](https://vike.dev/streaming) integration.)
+Benefits:
+ - Data is fetched at the component level (unlike [`+data`](https://vike.dev/data), which fetches at the page level).
+ - The rest of the page is eagerly rendered while the component waits for its data (see [Progressive Rendering](https://vike.dev/streaming#progressive-rendering)).
+ - All the niceties of Apollo GraphQL.
+
+You can completely stop using Vike's [`+data` hook](https://vike.dev/data) — or use both: `+data` for some pages, and `vike-react-apollo` for others.
+
+## Example
+
+See [examples/apollo/](https://github.com/vikejs/vike-react/tree/main/examples/apollo).
+
+
## `withFallback()`
@@ -200,10 +216,9 @@ function SomePageSection() {
-
## `` tags
-To set tags such as `` and `` based on fetched data, you can use [``, ``, and `useConfig()`](https://vike.dev/useConfig).
+To set tags such as `` and `` based on fetched data, you can use [`useConfig()` / `` / ``](https://vike.dev/useConfig#ui-components).
```js
import { useSuspenseQuery } from '@tanstack/react-query'
@@ -233,8 +248,10 @@ function Movies() {
}
```
-
+> [!NOTE]
+> The `` tag is only shown to bots. See the explanation at [Vike Docs > `useConfig` > HTML Streaming](https://vike.dev/useConfig#html-streaming).
+
## Error Handling
@@ -257,25 +274,25 @@ See: [`withFallback()`](#withfallback)
-
## How it works
-Upon SSR, the component is rendered to HTML and its data loaded on the server-side. On the client side, the component is merely [hydrated](https://vike.dev/hydration).
+On the server side (during SSR), the component is rendered to HTML and its data is loaded. On the client side, the component is just [hydrated](https://vike.dev/hydration): the data fetched on the server is passed to the client and reused.
Upon page navigation (and rendering the first page if [SSR is disabled](https://vike.dev/ssr)), the component is rendered and its data loaded on the client-side.
> [!NOTE]
-> With `vike-react-apollo` you fetch data on a component-level instead of using Vike's [`data()` hook](https://vike.dev/data) which fetches data on a page-level.
-
-> [!NOTE]
-> Behind the scenes `vike-react-apollo` integrates Apollo GraphQL into [the HTML stream](https://github.com/brillout/react-streaming#readme).
+> Behind the scenes `vike-react-apollo` integrates Apollo GraphQL into [`react-streaming`](https://github.com/brillout/react-streaming#readme).
+## Version history
+
+See [CHANGELOG.md](https://github.com/vikejs/vike-react/blob/main/packages/vike-react-apollo/CHANGELOG.md).
+
+
## See also
-- [Example](https://github.com/vikejs/vike-react/tree/main/examples/apollo)
- [Vike Docs > Apollo GraphQL](https://vike.dev/apollo-graphql)
- [Vike Docs > Data Fetching](https://vike.dev/data-fetching)
- [Apollo GraphQL > useSuspenseQuery](https://www.apollographql.com/docs/react/api/react/hooks/#usesuspensequery)
diff --git a/packages/vike-react-apollo/package.json b/packages/vike-react-apollo/package.json
index d2763bbe..5a17aea3 100644
--- a/packages/vike-react-apollo/package.json
+++ b/packages/vike-react-apollo/package.json
@@ -1,11 +1,12 @@
{
"name": "vike-react-apollo",
- "version": "0.1.1",
+ "version": "0.1.4",
+ "homepage": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-apollo#readme",
"type": "module",
- "main": "dist/src/index.js",
- "typings": "dist/src/index.js",
+ "main": "dist/index.js",
+ "typings": "dist/index.js",
"exports": {
- ".": "./dist/src/index.js",
+ ".": "./dist/index.js",
"./config": "./dist/integration/+config.js",
"./__internal/integration/Wrapper": "./dist/integration/Wrapper.js"
},
@@ -23,24 +24,24 @@
"react": ">=18.0.0",
"react-dom": ">=18.0.0",
"react-streaming": ">=0.3.41",
- "vike-react": ">=0.4.18"
+ "vike-react": ">=0.6.4"
},
"devDependencies": {
- "@brillout/release-me": "^0.4.2",
+ "@brillout/release-me": "^0.4.8",
"@apollo/client": "^3.10.8",
"@apollo/client-react-streaming": "^0.11.2",
"graphql": "^16.9.0",
- "@types/node": "^20.11.17",
- "@types/react": "^18.2.55",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "@types/react-dom": "^18.3.0",
- "react-streaming": "^0.3.43",
+ "@types/node": "^24.0.8",
+ "@types/react": "^19.1.13",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "@types/react-dom": "^19.1.9",
+ "react-streaming": "^0.4.11",
"rimraf": "^5.0.5",
- "typescript": "^5.3.3",
- "vike": "^0.4.211",
- "vike-react": "^0.5.11",
- "vite": "^5.4.0"
+ "typescript": "^5.9.2",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12",
+ "vite": "^7.1.7"
},
"dependencies": {
"react-error-boundary": "^4.0.12"
@@ -58,6 +59,5 @@
"files": [
"dist"
],
- "repository": "github:vikejs/vike-react",
"license": "MIT"
}
diff --git a/packages/vike-react-apollo/integration/+config.ts b/packages/vike-react-apollo/src/integration/+config.ts
similarity index 78%
rename from packages/vike-react-apollo/integration/+config.ts
rename to packages/vike-react-apollo/src/integration/+config.ts
index cfb69992..3086376e 100644
--- a/packages/vike-react-apollo/integration/+config.ts
+++ b/packages/vike-react-apollo/src/integration/+config.ts
@@ -1,16 +1,16 @@
export { config as default }
import type { Config } from 'vike/types'
-import 'vike-react/config' // Needed for declaration merging of Config
import type { ApolloClient } from '@apollo/client-react-streaming'
+import 'vike-react/config' // Needed for merging vike-react's Vike.Config such as +stream
const config = {
name: 'vike-react-apollo',
require: {
- 'vike-react': '>=0.4.18',
+ 'vike-react': '>=0.6.4',
},
Wrapper: 'import:vike-react-apollo/__internal/integration/Wrapper:Wrapper',
- streamIsRequired: true,
+ stream: { require: true },
meta: {
ApolloClient: {
env: {
diff --git a/packages/vike-react-apollo/integration/Transport.tsx b/packages/vike-react-apollo/src/integration/Transport.tsx
similarity index 100%
rename from packages/vike-react-apollo/integration/Transport.tsx
rename to packages/vike-react-apollo/src/integration/Transport.tsx
diff --git a/packages/vike-react-apollo/integration/Wrapper.tsx b/packages/vike-react-apollo/src/integration/Wrapper.tsx
similarity index 100%
rename from packages/vike-react-apollo/integration/Wrapper.tsx
rename to packages/vike-react-apollo/src/integration/Wrapper.tsx
diff --git a/packages/vike-react-apollo/utils/assert.ts b/packages/vike-react-apollo/src/utils/assert.ts
similarity index 100%
rename from packages/vike-react-apollo/utils/assert.ts
rename to packages/vike-react-apollo/src/utils/assert.ts
diff --git a/packages/vike-react-apollo/tsconfig.json b/packages/vike-react-apollo/tsconfig.json
index 714277ff..6da32466 100644
--- a/packages/vike-react-apollo/tsconfig.json
+++ b/packages/vike-react-apollo/tsconfig.json
@@ -15,10 +15,11 @@
// Output
"declaration": true,
"noEmitOnError": false,
- "rootDir": "./",
+ "rootDir": "./src/",
// Misc
"esModuleInterop": true,
"skipLibCheck": true,
"jsx": "react"
- }
+ },
+ "include": ["./src"]
}
diff --git a/packages/vike-react-chakra/CHANGELOG.md b/packages/vike-react-chakra/CHANGELOG.md
index 8dd8baf6..ab295ac3 100644
--- a/packages/vike-react-chakra/CHANGELOG.md
+++ b/packages/vike-react-chakra/CHANGELOG.md
@@ -1,3 +1,13 @@
+## [1.0.2](https://github.com/vikejs/vike-react/compare/vike-react-chakra@1.0.0...vike-react-chakra@1.0.2) (2025-07-01)
+
+
+### Bug Fixes
+
+* add eject.config.js ([0070495](https://github.com/vikejs/vike-react/commit/00704957fcf374ad0c7ebb0645a36b8d2035d2d2))
+* fix repo link on npm ([7a85501](https://github.com/vikejs/vike-react/commit/7a85501148774c871a342881cbe9f06678378754))
+
+
+
## [1.0.1](https://github.com/vikejs/vike-react/compare/vike-react-chakra@1.0.0...vike-react-chakra@1.0.1) (2024-11-28)
diff --git a/packages/vike-react-chakra/README.md b/packages/vike-react-chakra/README.md
index 78af21c5..88e13dba 100644
--- a/packages/vike-react-chakra/README.md
+++ b/packages/vike-react-chakra/README.md
@@ -1,14 +1,18 @@
+
+
+[](https://www.npmjs.com/package/vike-react-chakra)
+
# `vike-react-chakra`
+Integrates [Chakra UI](https://www.chakra-ui.com/) into your [`vike-react`](https://vike.dev/vike-react) app.
+
[Installation](#installation)
[Settings](#settings)
[Version history](https://github.com/vikejs/vike-react/blob/main/packages/vike-react-chakra/CHANGELOG.md)
-[See Also](#see-also)
+[See also](#see-also)
-Integrates [Chakra UI](https://www.chakra-ui.com/) to your [`vike-react`](https://vike.dev/vike-react) app.
-
## Installation
1. `npm install vike-react-chakra @chakra-ui/react @emotion/react`
@@ -24,7 +28,7 @@ Integrates [Chakra UI](https://www.chakra-ui.com/) to your [`vike-react`](https:
extends: [vikeReact, vikeReactChakra]
}
```
-3. You can now use Chakra at any of your components.
+3. You can now use Chakra in any of your components.
```jsx
import { HStack, Button } from '@chakra-ui/react'
diff --git a/packages/vike-react-chakra/package.json b/packages/vike-react-chakra/package.json
index fdc99645..2deb7d89 100644
--- a/packages/vike-react-chakra/package.json
+++ b/packages/vike-react-chakra/package.json
@@ -1,6 +1,7 @@
{
"name": "vike-react-chakra",
- "version": "1.0.1",
+ "version": "1.0.2",
+ "homepage": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-chakra#readme",
"type": "module",
"exports": {
"./config": "./dist/config.js",
@@ -21,16 +22,16 @@
"vike-react": ">=0.4.13"
},
"devDependencies": {
- "@brillout/release-me": "^0.4.2",
+ "@brillout/release-me": "^0.4.8",
"@chakra-ui/react": "^3.0.2",
"@emotion/react": "^11.13.3",
- "@types/react": "^18.2.55",
- "react": "^18.3.1",
+ "@types/react": "^19.1.13",
+ "react": "^19.2.0",
"rimraf": "^5.0.5",
- "typescript": "^5.5.3",
- "vike": "^0.4.211",
- "vike-react": "^0.5.11",
- "vite": "^5.4.0"
+ "typescript": "^5.9.2",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12",
+ "vite": "^7.1.7"
},
"typesVersions": {
"*": {
@@ -45,6 +46,5 @@
"files": [
"dist"
],
- "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-chakra",
"license": "MIT"
}
diff --git a/packages/vike-react-query/CHANGELOG.md b/packages/vike-react-query/CHANGELOG.md
index c4bc550a..3e54241e 100644
--- a/packages/vike-react-query/CHANGELOG.md
+++ b/packages/vike-react-query/CHANGELOG.md
@@ -1,3 +1,87 @@
+## [0.1.10](https://github.com/vikejs/vike-react/compare/vike-react-query@0.1.9...vike-react-query@0.1.10) (2025-10-07)
+
+
+### Bug Fixes
+
+* improve DX in dev ([899dcf1](https://github.com/vikejs/vike-react/commit/899dcf1d3bbeb13e0996856e2ec01e24150ace03))
+
+
+### Performance Improvements
+
+* remove server code from client-side bundles ([a51d68d](https://github.com/vikejs/vike-react/commit/a51d68d07727cafb0b4031d67babb9be37e362ef))
+
+
+### MINOR BREAKING CHANGES
+
+> [!NOTE]
+> We recommend ignoring `MINOR BREAKING CHANGES` unless this version breaks your app, see [Vike Versioning](https://vike.dev/versioning).
+
+* Update Vike to `0.4.242` or above
+
+
+
+## [0.1.9](https://github.com/vikejs/vike-react/compare/vike-react-query@0.1.8...vike-react-query@0.1.9) (2025-10-06)
+
+
+### Bug Fixes
+
+* also unsubscribe upon stream failure ([c3555ee](https://github.com/vikejs/vike-react/commit/c3555eefda8d2ff62f9b1a10fe434590cb11758a))
+
+
+
+## [0.1.8](https://github.com/vikejs/vike-react/compare/vike-react-query@0.1.7...vike-react-query@0.1.8) (2025-10-06)
+
+
+### Bug Fixes
+
+* unsubscribe query cache ([#193](https://github.com/vikejs/vike-react/issues/193)) ([2913ebe](https://github.com/vikejs/vike-react/commit/2913ebe284c8312f7d941c8c9730cd7a72c25524))
+
+
+### MINOR BREAKING CHANGES
+
+> [!NOTE]
+> We recommend ignoring `MINOR BREAKING CHANGES` unless this version breaks your app, see [Vike Versioning](https://vike.dev/versioning).
+
+* update vike-react to `0.6.8` or above
+
+
+
+## [0.1.7](https://github.com/vikejs/vike-react/compare/vike-react-query@0.1.6...vike-react-query@0.1.7) (2025-10-06)
+
+
+### Bug Fixes
+
+* also send pre-fetched queries to client ([#192](https://github.com/vikejs/vike-react/issues/192)) ([953930c](https://github.com/vikejs/vike-react/commit/953930cecd3baa1ec2ac5f0f8408e1151c915506))
+
+
+
+## [0.1.6](https://github.com/vikejs/vike-react/compare/vike-react-query@0.1.5...vike-react-query@0.1.6) (2025-09-16)
+
+
+### Bug Fixes
+
+* react-streaming@^0.4.4 ([6420a27](https://github.com/vikejs/vike-react/commit/6420a277e86d0cf829de21f2a22fcf070f1075cd))
+
+
+
+## [0.1.5](https://github.com/vikejs/vike-react/compare/vike-react-query@0.1.4...vike-react-query@0.1.5) (2025-07-01)
+
+
+### Bug Fixes
+
+* fix repo link on npm ([7a85501](https://github.com/vikejs/vike-react/commit/7a85501148774c871a342881cbe9f06678378754))
+
+
+
+## [0.1.4](https://github.com/vikejs/vike-react/compare/vike-react-query@0.1.3...vike-react-query@0.1.4) (2025-05-29)
+
+
+### Bug Fixes
+
+* update +stream usage ([#175](https://github.com/vikejs/vike-react/issues/175)) ([7a3d1d6](https://github.com/vikejs/vike-react/commit/7a3d1d601f0ff2ff45409d92b3226f544eaf24c7))
+
+
+
## [0.1.3](https://github.com/vikejs/vike-react/compare/vike-react-query@0.1.2...vike-react-query@0.1.3) (2024-12-04)
diff --git a/packages/vike-react-query/README.md b/packages/vike-react-query/README.md
index 3bb0e371..f68db6a3 100644
--- a/packages/vike-react-query/README.md
+++ b/packages/vike-react-query/README.md
@@ -4,29 +4,34 @@
# `vike-react-query`
-Enables your React components to fetch data using [TanStack Query](https://tanstack.com/query/latest). Powered by [HTML streaming](https://github.com/brillout/react-streaming#readme).
+Enables your React components to fetch data using [TanStack Query](https://tanstack.com/query/latest).
-> [!NOTE]
-> Includes:
-> - [Progressive rendering](https://vike.dev/streaming#progressive-rendering)
-> - [SSR benefits](https://github.com/brillout/react-streaming#ssr)
-> - Fallback upon loading and/or error
-> - [Caching](https://tanstack.com/query/latest/docs/framework/react/reference/useSuspenseQuery)
+Powered by [`react-streaming`](https://github.com/brillout/react-streaming#readme).
+
+Features:
+- [Progressive Rendering](https://vike.dev/streaming#progressive-rendering)
+- [SSR benefits](https://github.com/brillout/react-streaming#ssr)
+- Fallback upon loading and/or error
+- [Caching](https://tanstack.com/query/latest/docs/framework/react/reference/useSuspenseQuery)
+
+
+
+**Table of Contents**
[Installation](#installation)
[Basic usage](#basic-usage)
+[Example](#example)
[`withFallback()`](#withfallback)
[`` tags](#head-tags)
[Error Handling](#error-handling)
[Settings](#settings)
[Usage with Telefunc](#usage-with-telefunc)
[How it works](#how-it-works)
-[Version history](https://github.com/vikejs/vike-react/blob/main/packages/vike-react-query/CHANGELOG.md)
+[Version history](#version-history)
[See also](#see-also)
-
## Installation
1. `npm install @tanstack/react-query vike-react-query`
@@ -48,7 +53,6 @@ Enables your React components to fetch data using [TanStack Query](https://tanst
-
## Basic usage
```jsx
@@ -75,8 +79,20 @@ const Movie = ({ id }) => {
> [!NOTE]
> Even though [`useSuspenseQuery()`](https://tanstack.com/query/latest/docs/framework/react/reference/useSuspenseQuery) is imported from `@tanstack/react-query`, you need to install `vike-react-query` for it to work. (The `useSuspenseQuery()` hook requires an [HTML stream](https://vike.dev/streaming) integration.)
+Benefits:
+ - Data is fetched at the component level (unlike [`+data`](https://vike.dev/data), which fetches at the page level).
+ - The rest of the page is eagerly rendered while the component waits for its data (see [Progressive Rendering](https://vike.dev/streaming#progressive-rendering)).
+ - All the niceties of TanStack Query.
+
+You can completely stop using Vike's [`+data` hook](https://vike.dev/data) — or use both: `+data` for some pages, and `vike-react-query` for others.
+
+## Example
+
+See [examples/query/](https://github.com/vikejs/vike-react/tree/main/examples/query).
+
+
## `withFallback()`
@@ -180,10 +196,9 @@ function SomePageSection() {
-
## `` tags
-To set tags such as `` and `` based on fetched data, you can use [``, ``, and `useConfig()`](https://vike.dev/useConfig).
+To set tags such as `` and `` based on fetched data, you can use [`useConfig()` / `` / ``](https://vike.dev/useConfig#ui-components).
```js
import { useSuspenseQuery } from '@tanstack/react-query'
@@ -210,8 +225,10 @@ function Movies() {
}
```
-
+> [!NOTE]
+> The `` tag is only shown to bots. See the explanation at [Vike Docs > `useConfig` > HTML Streaming](https://vike.dev/useConfig#html-streaming).
+
## Error Handling
@@ -234,7 +251,6 @@ See: [`withFallback()`](#withfallback)
-
## Settings
You can modify the defaults defined by [`QueryClient`](https://tanstack.com/query/latest/docs/reference/QueryClient).
@@ -273,7 +289,6 @@ export default (pageContext) => ({
-
## Usage with Telefunc
You can use `vike-react-query` with [Telefunc](https://telefunc.com).
@@ -434,25 +449,26 @@ const Movies = withFallback(
-
## How it works
-Upon SSR, the component is rendered to HTML and its data loaded on the server-side. On the client side, the component is merely [hydrated](https://vike.dev/hydration).
+On the server side (during SSR), the component is rendered to HTML and its data is loaded. On the client side, the component is just [hydrated](https://vike.dev/hydration): the data fetched on the server is passed to the client and reused.
Upon page navigation (and rendering the first page if [SSR is disabled](https://vike.dev/ssr)), the component is rendered and its data loaded on the client-side.
> [!NOTE]
-> With `vike-react-query` you fetch data on a component-level instead of using Vike's [`data()` hook](https://vike.dev/data) which fetches data on a page-level.
-
-> [!NOTE]
-> Behind the scenes `vike-react-query` integrates TanStack Query into [the HTML stream](https://github.com/brillout/react-streaming#readme).
+> Behind the scenes `vike-react-query` integrates TanStack Query into [`react-streaming`](https://github.com/brillout/react-streaming#readme).
+## Version history
+
+See [CHANGELOG.md](https://github.com/vikejs/vike-react/blob/main/packages/vike-react-query/CHANGELOG.md).
+
+
## See also
-- [Example](https://github.com/vikejs/vike-react/tree/main/examples/react-query)
- [Vike Docs > TanStack Query](https://vike.dev/tanstack-query)
-- [TanStack Query > useSuspenseQuery](https://tanstack.com/query/latest/docs/framework/react/reference/useSuspenseQuery)
- [Vike Docs > Data Fetching](https://vike.dev/data-fetching)
+- [TanStack Query > useSuspenseQuery](https://tanstack.com/query/latest/docs/framework/react/reference/useSuspenseQuery)
+- [React > ``](https://react.dev/reference/react/Suspense)
diff --git a/packages/vike-react-query/integration/StreamedHydration.tsx b/packages/vike-react-query/integration/StreamedHydration.tsx
deleted file mode 100644
index c5bcadea..00000000
--- a/packages/vike-react-query/integration/StreamedHydration.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-export { StreamedHydration }
-
-import type { QueryClient } from '@tanstack/react-query'
-import { dehydrate, hydrate, DehydratedState } from '@tanstack/react-query'
-import { uneval } from 'devalue'
-import type { ReactNode } from 'react'
-import { useStream } from 'react-streaming'
-
-declare global {
- interface Window {
- _rqd_?: { push: (entry: DehydratedState) => void } | DehydratedState[]
- _rqc_?: () => void
- }
-}
-
-/**
- * This component is responsible for:
- * - dehydrating the query client on the server
- * - hydrating the query client on the client
- * - if react-streaming is not used, it doesn't do anything
- */
-function StreamedHydration({ client, children }: { client: QueryClient; children: ReactNode }) {
- const stream = useStream()
-
- // stream is only avaiable in SSR
- const isSSR = !!stream
-
- if (isSSR) {
- stream.injectToStream(
- ``,
- )
- client.getQueryCache().subscribe((event) => {
- if (['added', 'updated'].includes(event.type) && event.query.state.status === 'success')
- stream.injectToStream(
- ``,
- )
- })
- }
-
- if (!isSSR && Array.isArray(window._rqd_)) {
- const onEntry = (entry: DehydratedState) => {
- hydrate(client, entry)
- }
- for (const entry of window._rqd_) {
- onEntry(entry)
- }
- window._rqd_ = { push: onEntry }
- }
- return children
-}
diff --git a/packages/vike-react-query/package.json b/packages/vike-react-query/package.json
index 1e15b55e..1b63ba76 100644
--- a/packages/vike-react-query/package.json
+++ b/packages/vike-react-query/package.json
@@ -1,11 +1,12 @@
{
"name": "vike-react-query",
- "version": "0.1.3",
+ "version": "0.1.10",
+ "homepage": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-query#readme",
"type": "module",
- "main": "dist/src/index.js",
- "typings": "dist/src/index.js",
+ "main": "dist/index.js",
+ "typings": "dist/index.js",
"exports": {
- ".": "./dist/src/index.js",
+ ".": "./dist/index.js",
"./config": "./dist/integration/+config.js",
"./__internal/integration/Wrapper": "./dist/integration/Wrapper.js",
"./__internal/integration/FallbackErrorBoundary": "./dist/integration/FallbackErrorBoundary.js"
@@ -16,29 +17,30 @@
"release": "release-me patch",
"release:minor": "release-me minor",
"release:commit": "release-me commit",
- "test": "vitest run"
+ "test:units": "vitest run"
},
"peerDependencies": {
"@tanstack/react-query": ">=5.0.0",
"react": ">=18.0.0",
- "react-streaming": ">=0.3.42",
- "vike-react": ">=0.4.13"
+ "react-streaming": ">=0.4.6",
+ "vike-react": ">=0.6.8",
+ "vike": ">=0.4.242"
},
"devDependencies": {
- "@brillout/release-me": "^0.4.2",
+ "@brillout/release-me": "^0.4.8",
"@tanstack/react-query": "^5.20.1",
"@testing-library/react": "^14.2.1",
- "@types/node": "^20.11.17",
- "@types/react": "^18.2.55",
+ "@types/node": "^24.0.8",
+ "@types/react": "^19.1.13",
"jsdom": "^24.0.0",
- "react": "^18.3.1",
- "react-streaming": "^0.3.43",
+ "react": "^19.2.0",
+ "react-streaming": "^0.4.11",
"rimraf": "^5.0.5",
- "typescript": "^5.5.3",
- "vike": "^0.4.211",
- "vike-react": "^0.5.11",
- "vite": "^5.4.0",
- "vitest": "^1.2.2"
+ "typescript": "^5.9.2",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12",
+ "vite": "^7.1.7",
+ "vitest": "^3.2.4"
},
"dependencies": {
"devalue": "^4.3.2",
@@ -57,6 +59,5 @@
"files": [
"dist"
],
- "repository": "github:vikejs/vike-react",
"license": "MIT"
}
diff --git a/packages/vike-react-query/integration/+config.ts b/packages/vike-react-query/src/integration/+config.ts
similarity index 85%
rename from packages/vike-react-query/integration/+config.ts
rename to packages/vike-react-query/src/integration/+config.ts
index cb260070..0d8e31bf 100644
--- a/packages/vike-react-query/integration/+config.ts
+++ b/packages/vike-react-query/src/integration/+config.ts
@@ -3,17 +3,18 @@ export { config as default }
import type { QueryClientConfig } from '@tanstack/react-query'
import type { ReactNode } from 'react'
import type { Config, ImportString } from 'vike/types'
-import 'vike-react/config' // Needed for declaration merging of Config
+import 'vike-react/config' // Needed for merging vike-react's Vike.Config such as +stream
const config = {
name: 'vike-react-query',
require: {
- 'vike-react': '>=0.4.13',
+ vike: '>=0.4.242',
+ 'vike-react': '>=0.6.4',
},
queryClientConfig: undefined,
Wrapper: 'import:vike-react-query/__internal/integration/Wrapper:Wrapper',
FallbackErrorBoundary: 'import:vike-react-query/__internal/integration/FallbackErrorBoundary:FallbackErrorBoundary',
- streamIsRequired: true,
+ stream: { require: true },
meta: {
queryClientConfig: {
env: {
diff --git a/packages/vike-react-query/integration/FallbackErrorBoundary.tsx b/packages/vike-react-query/src/integration/FallbackErrorBoundary.tsx
similarity index 72%
rename from packages/vike-react-query/integration/FallbackErrorBoundary.tsx
rename to packages/vike-react-query/src/integration/FallbackErrorBoundary.tsx
index f128c05f..2fba22ec 100644
--- a/packages/vike-react-query/integration/FallbackErrorBoundary.tsx
+++ b/packages/vike-react-query/src/integration/FallbackErrorBoundary.tsx
@@ -5,11 +5,7 @@ import React, { CSSProperties, ReactElement } from 'react'
import { ErrorBoundary, FallbackProps } from 'react-error-boundary'
function FallbackErrorBoundary({ children }: { children: ReactElement }) {
- /* TODO: either remove this or properly check whether env is DEV:
- * - Safe check against process.env.NODE_ENV for server-side
- * - Safe check against import.meta.env.DEV for client-side
- */
- return (false as boolean) /*import.meta.env.DEV*/ ? (
+ return globalThis.__VIKE__IS_DEV ? (
{({ reset }) => (
@@ -29,13 +25,7 @@ function Fallback({ resetErrorBoundary, error }: FallbackProps) {
- {
- /* TODO: either remove this or properly check whether env is DEV:
- * - Safe check against process.env.NODE_ENV for server-side
- * - Safe check against import.meta.env.DEV for client-side
- */
- (false as boolean) /*import.meta.env.DEV*/ && {getErrorStack(error)}
- }
+ {globalThis.__VIKE__IS_DEV && {getErrorStack(error)}}
)
}
diff --git a/packages/vike-react-query/src/integration/StreamedHydration.tsx b/packages/vike-react-query/src/integration/StreamedHydration.tsx
new file mode 100644
index 00000000..5612733d
--- /dev/null
+++ b/packages/vike-react-query/src/integration/StreamedHydration.tsx
@@ -0,0 +1,86 @@
+export { StreamedHydration }
+
+import type { QueryClient } from '@tanstack/react-query'
+import { dehydrate, hydrate, type DehydratedState } from '@tanstack/react-query'
+import { assert } from '../utils/assert.js'
+import { uneval } from 'devalue'
+import type { ReactNode } from 'react'
+import { useStream } from 'react-streaming'
+
+declare global {
+ interface Window {
+ _rqd_?: { push: (entry: DehydratedState) => void } | DehydratedState[]
+ _rqc_?: () => void
+ }
+}
+
+/**
+ * This component is responsible for:
+ * - dehydrating the query client on the server
+ * - hydrating the query client on the client
+ * - if react-streaming is not used, it doesn't do anything
+ */
+function StreamedHydration({ client, children }: { client: QueryClient; children: ReactNode }) {
+ const stream = useStream()
+
+ if (!globalThis.__VIKE__IS_CLIENT) {
+ assert(stream)
+ stream.injectToStream(
+ ``,
+ )
+
+ const alreadySent = new Set()
+
+ const unsubscribe = client.getQueryCache().subscribe((event) => {
+ if (stream.hasStreamEnded() || event.query.state.status !== 'success') return
+
+ let shouldSend = false
+ switch (event.type) {
+ case 'added':
+ // Also `observerAdded` and `observerResultsUpdated` for queries pre-fetched before subscription.
+ // https://github.com/vikejs/vike-react/pull/192
+ case 'observerAdded':
+ case 'observerResultsUpdated':
+ if (!alreadySent.has(event.query.queryHash)) {
+ alreadySent.add(event.query.queryHash)
+ shouldSend = true
+ }
+ break
+ case 'updated':
+ // Always send on `updated` events (even if already sent once), since updates may change the query data.
+ shouldSend = true
+ break
+ }
+ if (!shouldSend) return
+
+ stream.injectToStream(
+ ``,
+ )
+ })
+
+ // Unsubscribe
+ stream.streamEnd.then(() => {
+ unsubscribe()
+ })
+ // Properly handling rejection is complex, but luckily streamEnd never rejects
+ // https://github.com/brillout/promise-forwarding
+ stream.streamEnd.catch(() => assert(false)) // streamEnd never rejects
+ }
+
+ if (globalThis.__VIKE__IS_CLIENT && Array.isArray(window._rqd_)) {
+ const onEntry = (entry: DehydratedState) => {
+ hydrate(client, entry)
+ }
+ for (const entry of window._rqd_) {
+ onEntry(entry)
+ }
+ window._rqd_ = { push: onEntry }
+ }
+ return children
+}
diff --git a/packages/vike-react-query/integration/Wrapper.tsx b/packages/vike-react-query/src/integration/Wrapper.tsx
similarity index 52%
rename from packages/vike-react-query/integration/Wrapper.tsx
rename to packages/vike-react-query/src/integration/Wrapper.tsx
index 9fbcc3fa..9bade561 100644
--- a/packages/vike-react-query/integration/Wrapper.tsx
+++ b/packages/vike-react-query/src/integration/Wrapper.tsx
@@ -28,18 +28,15 @@ function PassThrough({ children }: any) {
let clientQueryClient: QueryClient | undefined
function getQueryClient(config: QueryClientConfig | undefined) {
- if (!isBrowser()) return new QueryClient(config)
- // React may throw away a partially rendered tree if it suspends, and then start again from scratch.
- // If it's no suspense boundary between the creation of queryClient and useSuspenseQuery,
- // then the entire tree is thrown away, including the creation of queryClient, which may produce infinity refetchs
- // https://github.com/TanStack/query/issues/6116#issuecomment-1904051005
- // https://github.com/vikejs/vike-react/pull/157
- if (!clientQueryClient) clientQueryClient = new QueryClient(config)
- return clientQueryClient
-}
-
-function isBrowser() {
- // Using `typeof window !== 'undefined'` alone is not enough because some users use https://www.npmjs.com/package/ssr-window
- return typeof window !== 'undefined' && typeof window.scrollY === 'number'
- // Alternatively, test whether environment is a *real* browser: https://github.com/brillout/picocolors/blob/d59a33a0fd52a8a33e4158884069192a89ce0113/picocolors.js#L87-L89
+ if (!globalThis.__VIKE__IS_CLIENT) {
+ return new QueryClient(config)
+ } else {
+ // React may throw away a partially rendered tree if it suspends, and then start again from scratch.
+ // If it's no suspense boundary between the creation of queryClient and useSuspenseQuery,
+ // then the entire tree is thrown away, including the creation of queryClient, which may produce infinity refetchs
+ // https://github.com/TanStack/query/issues/6116#issuecomment-1904051005
+ // https://github.com/vikejs/vike-react/pull/157
+ if (!clientQueryClient) clientQueryClient = new QueryClient(config)
+ return clientQueryClient
+ }
}
diff --git a/packages/vike-react-query/src/utils/assert.ts b/packages/vike-react-query/src/utils/assert.ts
new file mode 100644
index 00000000..6910f537
--- /dev/null
+++ b/packages/vike-react-query/src/utils/assert.ts
@@ -0,0 +1,4 @@
+export function assert(condition: unknown): asserts condition {
+ if (condition) return
+ throw new Error('You stumbled upon a vike-react-query bug, reach out on GitHub.')
+}
diff --git a/packages/vike-react-query/tsconfig.json b/packages/vike-react-query/tsconfig.json
index 714277ff..6da32466 100644
--- a/packages/vike-react-query/tsconfig.json
+++ b/packages/vike-react-query/tsconfig.json
@@ -15,10 +15,11 @@
// Output
"declaration": true,
"noEmitOnError": false,
- "rootDir": "./",
+ "rootDir": "./src/",
// Misc
"esModuleInterop": true,
"skipLibCheck": true,
"jsx": "react"
- }
+ },
+ "include": ["./src"]
}
diff --git a/packages/vike-react-redux/.gitignore b/packages/vike-react-redux/.gitignore
new file mode 100644
index 00000000..b0a5c349
--- /dev/null
+++ b/packages/vike-react-redux/.gitignore
@@ -0,0 +1,2 @@
+/node_modules/
+/dist/
diff --git a/packages/vike-react-redux/CHANGELOG.md b/packages/vike-react-redux/CHANGELOG.md
new file mode 100644
index 00000000..cf898910
--- /dev/null
+++ b/packages/vike-react-redux/CHANGELOG.md
@@ -0,0 +1,13 @@
+## [0.1.1](https://github.com/vikejs/vike-react/compare/vike-react-redux@0.1.0...vike-react-redux@0.1.1) (2025-07-01)
+
+
+### Bug Fixes
+
+* fix repo link on npm ([7a85501](https://github.com/vikejs/vike-react/commit/7a85501148774c871a342881cbe9f06678378754))
+
+
+
+# 0.1.0 (2025-05-20)
+
+
+
diff --git a/packages/vike-react-redux/README.md b/packages/vike-react-redux/README.md
new file mode 100644
index 00000000..aaa4175c
--- /dev/null
+++ b/packages/vike-react-redux/README.md
@@ -0,0 +1,185 @@
+
+
+[](https://www.npmjs.com/package/vike-react-redux)
+
+# `vike-react-redux`
+
+Integrates [Redux](https://react-redux.js.org) into your [`vike-react`](https://vike.dev/vike-react) app.
+
+[Installation](#installation)
+[Example](#example)
+[Settings](#settings)
+[Populate store with `+data`](#populate-store-with-data)
+[Version history](#version-history)
+[What it does](#what-it-does)
+[See Also](#see-also)
+
+
+
+## Installation
+
+1. `npm install vike-react-redux react-redux @reduxjs/toolkit`
+2. Extend `+config.js`:
+ ```js
+ // pages/+config.js
+
+ import vikeReact from "vike-react/config"
+ import vikeReactRedux from "vike-react-redux/config"
+
+ export default {
+ // ...
+ extends: [vikeReact, vikeReactRedux]
+ }
+ ```
+3. Create `+redux.js` file:
+ ```js
+ // pages/+redux.js
+ // Environemnt: client, server
+
+ import { createStore } from '../store/createStore'
+ export default { createStore }
+ ```
+ ```ts
+ // store/createStore.ts
+
+ export { createStore }
+ export type AppStore = ReturnType
+ export type RootState = ReturnType
+ export type AppDispatch = AppStore['dispatch']
+
+ import { combineReducers, configureStore } from '@reduxjs/toolkit'
+ import { countReducer } from './slices/count'
+ import { todosReducer } from './slices/todos'
+ const reducer = combineReducers({ count: countReducer, todos: todosReducer })
+
+ function createStore(pageContext) {
+ const preloadedState = pageContext.isClientSide ? pageContext.redux.ssrState : undefined
+ return configureStore({ reducer, preloadedState })
+ }
+ ```
+ ```ts
+ // store/hooks.ts
+
+ // This file serves as a central hub for re-exporting pre-typed Redux hooks.
+ import { useDispatch, useSelector, useStore } from 'react-redux'
+ import type { AppDispatch, AppStore, RootState } from './createStore'
+
+ // Use throughout your app instead of plain `useDispatch` and `useSelector`
+ export const useAppDispatch = useDispatch.withTypes()
+ export const useAppSelector = useSelector.withTypes()
+ export const useAppStore = useStore.withTypes()
+ ```
+4. You can now use Redux at any of your components.
+ ```tsx
+ // components/Counter.tsx
+
+ export { Counter }
+
+ import React from 'react'
+ import { useAppDispatch, useAppSelector } from '../store/hooks'
+ import { increment, selectCount } from '../store/slices/count'
+
+ function Counter() {
+ const dispatch = useAppDispatch()
+ const count = useAppSelector(selectCount)
+ return (
+
+ )
+ }
+ ```
+
+
+
+## Example
+
+See [examples/redux](https://github.com/vikejs/vike-react/tree/main/examples/redux).
+
+
+
+## Settings
+
+The only `+redux` setting is `createStore()` as documented at [Installation](#installation).
+
+**Install only for some pages**
+
+You can remove the `vike-react-redux` integration for [some of your pages](https://vike.dev/config#inheritance):
+
+```js
+// pages/about/+redux.js
+
+export const redux = null
+```
+
+**Custom integration**
+
+For full customization consider [ejecting](https://vike.dev/eject).
+
+> [!NOTE]
+> Consider making a [Pull Request before ejecting](https://vike.dev/eject#when-to-eject).
+
+
+
+## Populate store with `+data`
+
+To populate your store with data fetched via the [`+data`](https://vike.dev/data) hook, use [`+onData`](https://vike.dev/onData) and [`pageContext.data`](https://vike.dev/pageContext#data).
+
+```ts
+// pages/todos/+onData.ts
+// Environment: server, client
+export { onData }
+
+import type { PageContext } from 'vike/types'
+import type { Data } from './+data'
+import { initializeTodos } from '../../store/slices/todos'
+
+function onData(pageContext: PageContext & { data?: Data }) {
+ const { store } = pageContext
+ store.dispatch(initializeTodos(pageContext.data!.todoItemsInitial))
+
+ // Saving KBs: we don't need pageContext.data (we use the store instead)
+ // - If we don't delete pageContext.data then Vike sends pageContext.data to the client-side
+ // - This optimization only works if the page is SSR'd: if the page is pre-rendered then don't do this
+ delete pageContext.data
+}
+```
+
+See To-Do List example at [examples/redux/](https://github.com/vikejs/vike-react/tree/main/examples/redux).
+
+> [!NOTE]
+> During [SSR](https://vike.dev/ssr), `+onData` is called only on the server. That's because the store state is sent to the client, so that when the page hydrates, the client has the exact same state as the server — preventing [hydration mismatches](https://vike.dev/hydration-mismatch).
+>
+> As a result, the store doesn't need to be populated on the client: it's already populated on the server and then sent to the client.
+>
+> See also: [What it does](#what-it-does).
+
+
+
+## Version history
+
+See [CHANGELOG.md](https://github.com/vikejs/vike-react/blob/main/packages/vike-react-redux/CHANGELOG.md).
+
+
+
+## What it does
+
+`vike-react-redux` does the following:
+ - Initializes the store. (Using [`+onCreatePageContext.server`](https://vike.dev/onCreatePageContext), [`+onAfterRenderHtml.server`](https://vike.dev/onAfterRenderHtml), and [`+onBeforeRenderClient.client`](https://vike.dev/onBeforeRenderClient).)
+ - Installs Redux's [``](https://react-redux.js.org/api/provider).
+ - Passes the initial state (`pageContext.redux.ssrState`) used during [SSR](https://vike.dev/ssr) to the client. (To ensure that the same state is used for hydration, preventing hydration mismatches.)
+
+For more details, have a look at the source code of `vike-react-redux` (it's tiny!).
+
+You can learn more at:
+ - [Vike > Store (State Management) > SSR](https://vike.dev/store#ssr)
+ - [Redux > Server Side Rendering](https://redux.js.org/usage/server-rendering)
+
+
+
+## See also
+
+- [Example](https://github.com/vikejs/vike-react/tree/main/examples/redux)
+- [Vike Docs > Redux](https://vike.dev/redux)
+- [Vike Docs > Store](https://vike.dev/store)
+- [React Redux](https://react-redux.js.org)
diff --git a/packages/vike-react-redux/Wrapper.tsx b/packages/vike-react-redux/Wrapper.tsx
new file mode 100644
index 00000000..9d35b242
--- /dev/null
+++ b/packages/vike-react-redux/Wrapper.tsx
@@ -0,0 +1,18 @@
+export { Wrapper }
+
+import React from 'react'
+import { Provider } from 'react-redux'
+import { usePageContext } from 'vike-react/usePageContext'
+import type { Store } from '@reduxjs/toolkit'
+
+function Wrapper({ children }: { children: React.ReactNode }) {
+ const pageContext = usePageContext()
+ let store: undefined | Store
+ if (pageContext.isClientSide) {
+ store = pageContext.globalContext.store
+ } else {
+ store = pageContext.store
+ }
+ if (!store) return <>{children}>
+ return {children}
+}
diff --git a/packages/vike-react-redux/config.ts b/packages/vike-react-redux/config.ts
new file mode 100644
index 00000000..389a16b2
--- /dev/null
+++ b/packages/vike-react-redux/config.ts
@@ -0,0 +1,46 @@
+export { config as default }
+
+import type { Config } from 'vike/types'
+import type { Store } from '@reduxjs/toolkit'
+
+const config = {
+ name: 'vike-react-redux',
+ require: {
+ vike: '>=0.4.230',
+ 'vike-react': '>=0.6.3',
+ },
+
+ passToClient: ['redux.ssrState'],
+
+ meta: {
+ redux: {
+ env: { server: true, client: true },
+ global: true,
+ },
+ },
+
+ onCreatePageContext: 'import:vike-react-redux/__internal/onCreatePageContext:onCreatePageContext',
+ onAfterRenderHtml: 'import:vike-react-redux/__internal/onAfterRenderHtml:onAfterRenderHtml',
+ onBeforeRenderClient: 'import:vike-react-redux/__internal/onBeforeRenderClient:onBeforeRenderClient',
+ Wrapper: 'import:vike-react-redux/__internal/Wrapper:Wrapper',
+} satisfies Config
+
+declare global {
+ namespace Vike {
+ interface Config {
+ redux?: {
+ createStore: (pageContext: PageContext | GlobalContextClient) => Store
+ }
+ }
+ interface PageContext {
+ // vike-react-redux only defines pageContext.redux.store on the server-side, but thanks to https://github.com/vikejs/vike/pull/2459 the store is also avaiable at pageContext.redux.store on the client-side: on the client-side pageContext.redux.store falls back to globalContext.store
+ store: Store
+ redux?: {
+ ssrState?: Record
+ }
+ }
+ interface GlobalContextClient {
+ store: Store
+ }
+ }
+}
diff --git a/packages/vike-react-redux/onAfterRenderHtml.server.ts b/packages/vike-react-redux/onAfterRenderHtml.server.ts
new file mode 100644
index 00000000..a3c091cc
--- /dev/null
+++ b/packages/vike-react-redux/onAfterRenderHtml.server.ts
@@ -0,0 +1,10 @@
+export { onAfterRenderHtml }
+
+import type { PageContextServer } from 'vike/types'
+
+function onAfterRenderHtml(pageContext: PageContextServer) {
+ const configRedux = pageContext.config.redux
+ if (!configRedux) return
+ pageContext.redux ??= {}
+ pageContext.redux.ssrState = pageContext.store.getState()
+}
diff --git a/packages/vike-react-redux/onBeforeRenderClient.client.ts b/packages/vike-react-redux/onBeforeRenderClient.client.ts
new file mode 100644
index 00000000..24378d09
--- /dev/null
+++ b/packages/vike-react-redux/onBeforeRenderClient.client.ts
@@ -0,0 +1,9 @@
+export { onBeforeRenderClient }
+
+import type { PageContextClient } from 'vike/types'
+
+function onBeforeRenderClient(pageContext: PageContextClient) {
+ const configRedux = pageContext.config.redux
+ if (!configRedux) return
+ pageContext.globalContext.store ??= configRedux.createStore(pageContext)
+}
diff --git a/packages/vike-react-redux/onCreatePageContext.server.ts b/packages/vike-react-redux/onCreatePageContext.server.ts
new file mode 100644
index 00000000..0c7043d4
--- /dev/null
+++ b/packages/vike-react-redux/onCreatePageContext.server.ts
@@ -0,0 +1,9 @@
+export { onCreatePageContext }
+
+import type { PageContextServer } from 'vike/types'
+
+function onCreatePageContext(pageContext: PageContextServer) {
+ const configRedux = pageContext.config.redux
+ if (!configRedux) return
+ pageContext.store = configRedux.createStore(pageContext)
+}
diff --git a/packages/vike-react-redux/package.json b/packages/vike-react-redux/package.json
new file mode 100644
index 00000000..838b7ced
--- /dev/null
+++ b/packages/vike-react-redux/package.json
@@ -0,0 +1,60 @@
+{
+ "name": "vike-react-redux",
+ "version": "0.1.1",
+ "homepage": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-redux#readme",
+ "type": "module",
+ "exports": {
+ "./config": "./dist/config.js",
+ "./__internal/onCreatePageContext": "./dist/onCreatePageContext.server.js",
+ "./__internal/onAfterRenderHtml": "./dist/onAfterRenderHtml.server.js",
+ "./__internal/onBeforeRenderClient": "./dist/onBeforeRenderClient.client.js",
+ "./__internal/Wrapper": "./dist/Wrapper.js"
+ },
+ "scripts": {
+ "dev": "tsc --watch",
+ "build": "rimraf dist/ && tsc",
+ "release": "release-me patch",
+ "release:minor": "release-me minor",
+ "release:major": "release-me major",
+ "release:commit": "release-me commit"
+ },
+ "peerDependencies": {
+ "react-redux": ">=9",
+ "react": ">=18",
+ "vike": ">=0.4.230",
+ "vike-react": ">=0.6.3"
+ },
+ "devDependencies": {
+ "@brillout/release-me": "^0.4.8",
+ "@reduxjs/toolkit": "^2.8.2",
+ "@types/react": "^19.1.13",
+ "react": "^19.2.0",
+ "rimraf": "^5.0.5",
+ "typescript": "^5.9.2",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12"
+ },
+ "typesVersions": {
+ "*": {
+ "config": [
+ "dist/config.d.ts"
+ ],
+ "__internal/onAfterRenderHtml": [
+ "dist/onAfterRenderHtml.d.ts"
+ ],
+ "__internal/onCreatePageContext": [
+ "dist/onCreatePageContext.server.d.ts"
+ ],
+ "__internal/onBeforeRenderClient": [
+ "dist/onBeforeRenderClient.d.ts"
+ ],
+ "__internal/Wrapper": [
+ "dist/Wrapper.d.ts"
+ ]
+ }
+ },
+ "files": [
+ "dist"
+ ],
+ "license": "MIT"
+}
diff --git a/packages/vike-react-redux/tsconfig.json b/packages/vike-react-redux/tsconfig.json
new file mode 100644
index 00000000..ee30dd23
--- /dev/null
+++ b/packages/vike-react-redux/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "declaration": true,
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "jsx": "react",
+ "outDir": "./dist/",
+ "skipLibCheck": true,
+ "types": ["vike-react"],
+ // Strictness
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "noImplicitAny": true
+ }
+}
diff --git a/packages/vike-react-styled-components/CHANGELOG.md b/packages/vike-react-styled-components/CHANGELOG.md
index 2753e012..f772c1ea 100644
--- a/packages/vike-react-styled-components/CHANGELOG.md
+++ b/packages/vike-react-styled-components/CHANGELOG.md
@@ -1,3 +1,12 @@
+## [1.0.3](https://github.com/vikejs/vike-react/compare/vike-react-styled-components@1.0.2...vike-react-styled-components@1.0.3) (2025-07-01)
+
+
+### Bug Fixes
+
+* fix repo link on npm ([7a85501](https://github.com/vikejs/vike-react/commit/7a85501148774c871a342881cbe9f06678378754))
+
+
+
## [1.0.2](https://github.com/vikejs/vike-react/compare/vike-react-styled-components@1.0.1...vike-react-styled-components@1.0.2) (2024-12-28)
diff --git a/packages/vike-react-styled-components/README.md b/packages/vike-react-styled-components/README.md
index 5ff23c6a..e39ce2cd 100644
--- a/packages/vike-react-styled-components/README.md
+++ b/packages/vike-react-styled-components/README.md
@@ -1,6 +1,10 @@
+
+
+[](https://www.npmjs.com/package/vike-react-styled-components)
+
# `vike-react-styled-components`
-Integrates [styled-components](https://styled-components.com) to your [`vike-react`](https://vike.dev/vike-react) app.
+Integrates [styled-components](https://styled-components.com) into your [`vike-react`](https://vike.dev/vike-react) app.
[Installation](#installation)
[Settings](#settings)
@@ -48,7 +52,7 @@ Integrates [styled-components](https://styled-components.com) to your [`vike-rea
}
```
-4. You can now use `styled-components` at any of your components.
+4. You can now use `styled-components` in any of your components.
```jsx
import { styled } from "styled-components";
diff --git a/packages/vike-react-styled-components/package.json b/packages/vike-react-styled-components/package.json
index 2d0d213f..4c5ff7a5 100644
--- a/packages/vike-react-styled-components/package.json
+++ b/packages/vike-react-styled-components/package.json
@@ -1,6 +1,7 @@
{
"name": "vike-react-styled-components",
- "version": "1.0.2",
+ "version": "1.0.3",
+ "homepage": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-styled-components#readme",
"type": "module",
"exports": {
"./config": "./dist/config.js",
@@ -22,15 +23,15 @@
"vike-react": ">=0.4.13"
},
"devDependencies": {
- "@brillout/release-me": "^0.4.2",
- "@types/react": "^18.2.55",
- "react": "^18.3.1",
+ "@brillout/release-me": "^0.4.8",
+ "@types/react": "^19.1.13",
+ "react": "^19.2.0",
"rimraf": "^5.0.5",
"styled-components": "^6.1.13",
- "typescript": "^5.5.3",
- "vike": "^0.4.211",
- "vike-react": "^0.5.11",
- "vite": "^5.4.0"
+ "typescript": "^5.9.2",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12",
+ "vite": "^7.1.7"
},
"typesVersions": {
"*": {
@@ -51,6 +52,5 @@
"files": [
"dist"
],
- "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-styled-components",
"license": "MIT"
}
diff --git a/packages/vike-react-styled-jsx/CHANGELOG.md b/packages/vike-react-styled-jsx/CHANGELOG.md
index 140851ec..16fe37b0 100644
--- a/packages/vike-react-styled-jsx/CHANGELOG.md
+++ b/packages/vike-react-styled-jsx/CHANGELOG.md
@@ -1,3 +1,12 @@
+## [1.0.3](https://github.com/vikejs/vike-react/compare/vike-react-styled-jsx@1.0.2...vike-react-styled-jsx@1.0.3) (2025-07-01)
+
+
+### Bug Fixes
+
+* fix repo link on npm ([7a85501](https://github.com/vikejs/vike-react/commit/7a85501148774c871a342881cbe9f06678378754))
+
+
+
## [1.0.2](https://github.com/vikejs/vike-react/compare/vike-react-styled-jsx@1.0.1...vike-react-styled-jsx@1.0.2) (2024-12-28)
diff --git a/packages/vike-react-styled-jsx/README.md b/packages/vike-react-styled-jsx/README.md
index ada094a1..5de8ef11 100644
--- a/packages/vike-react-styled-jsx/README.md
+++ b/packages/vike-react-styled-jsx/README.md
@@ -1,6 +1,10 @@
+
+
+[](https://www.npmjs.com/package/vike-react-styled-jsx)
+
# `vike-react-styled-jsx`
-Integrates [styled-jsx](https://github.com/vercel/styled-jsx) to your [`vike-react`](https://vike.dev/vike-react) app.
+Integrates [styled-jsx](https://github.com/vercel/styled-jsx) into your [`vike-react`](https://vike.dev/vike-react) app.
[Installation](#installation)
[Settings](#settings)
@@ -44,7 +48,7 @@ Integrates [styled-jsx](https://github.com/vercel/styled-jsx) to your [`vike-rea
}
```
-4. You can now use `styled-jsx` at any of your components.
+4. You can now use `styled-jsx` in any of your components.
```jsx
function SomeComponent() {
return (
diff --git a/packages/vike-react-styled-jsx/package.json b/packages/vike-react-styled-jsx/package.json
index dddecbe2..cca39b50 100644
--- a/packages/vike-react-styled-jsx/package.json
+++ b/packages/vike-react-styled-jsx/package.json
@@ -1,6 +1,7 @@
{
"name": "vike-react-styled-jsx",
- "version": "1.0.2",
+ "version": "1.0.3",
+ "homepage": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-styled-jsx#readme",
"type": "module",
"exports": {
"./config": "./dist/config.js",
@@ -22,15 +23,15 @@
"vike-react": ">=0.4.13"
},
"devDependencies": {
- "@brillout/release-me": "^0.4.2",
- "@types/react": "^18.2.55",
- "react": "^18.3.1",
+ "@brillout/release-me": "^0.4.8",
+ "@types/react": "^19.1.13",
+ "react": "^19.2.0",
"rimraf": "^5.0.5",
"styled-jsx": "^5.1.6",
- "typescript": "^5.5.3",
- "vike": "^0.4.211",
- "vike-react": "^0.5.11",
- "vite": "^5.4.0"
+ "typescript": "^5.9.2",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12",
+ "vite": "^7.1.7"
},
"typesVersions": {
"*": {
@@ -51,6 +52,5 @@
"files": [
"dist"
],
- "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-styled-jsx",
"license": "MIT"
}
diff --git a/packages/vike-react-zustand/.gitignore b/packages/vike-react-zustand/.gitignore
new file mode 100644
index 00000000..b0a5c349
--- /dev/null
+++ b/packages/vike-react-zustand/.gitignore
@@ -0,0 +1,2 @@
+/node_modules/
+/dist/
diff --git a/packages/vike-react-zustand/CHANGELOG.md b/packages/vike-react-zustand/CHANGELOG.md
new file mode 100644
index 00000000..247b4305
--- /dev/null
+++ b/packages/vike-react-zustand/CHANGELOG.md
@@ -0,0 +1,57 @@
+## [0.1.5](https://github.com/vikejs/vike-react/compare/vike-react-zustand@0.1.4...vike-react-zustand@0.1.5) (2025-09-16)
+
+
+### Bug Fixes
+
+* react-streaming@^0.4.4 ([6420a27](https://github.com/vikejs/vike-react/commit/6420a277e86d0cf829de21f2a22fcf070f1075cd))
+
+
+
+## [0.1.4](https://github.com/vikejs/vike-react/compare/vike-react-zustand@0.1.3...vike-react-zustand@0.1.4) (2025-09-04)
+
+
+### Performance Improvements
+
+* use hook filters ([#183](https://github.com/vikejs/vike-react/issues/183)) ([949b5c6](https://github.com/vikejs/vike-react/commit/949b5c678ee0923f26416ac3992e3b2f56da7907))
+
+
+### MINOR BREAKING CHANGES
+
+> [!NOTE]
+> We recommend ignoring `MINOR BREAKING CHANGES` unless this version breaks your app, see [Vike Versioning](https://vike.dev/versioning).
+
+* Update to Vite `6.3.0` or above
+
+
+
+## [0.1.3](https://github.com/vikejs/vike-react/compare/vike-react-zustand@0.1.2...vike-react-zustand@0.1.3) (2025-08-18)
+
+
+### Bug Fixes
+
+* avoid Vite type version mismatch ([c58b61a](https://github.com/vikejs/vike-react/commit/c58b61ab0c22e3781a65575202ba634dcebd6496))
+
+
+
+## [0.1.2](https://github.com/vikejs/vike-react/compare/vike-react-zustand@0.1.1...vike-react-zustand@0.1.2) (2025-07-11)
+
+
+### Bug Fixes
+
+* automatically enable +stream (fix vikejs/vike[#2549](https://github.com/vikejs/vike-react/issues/2549)) ([6395032](https://github.com/vikejs/vike-react/commit/63950323133ddfac47e418c82e29490ee6efce4a))
+
+
+
+## [0.1.1](https://github.com/vikejs/vike-react/compare/vike-react-zustand@0.1.0...vike-react-zustand@0.1.1) (2025-07-01)
+
+
+### Bug Fixes
+
+* fix repo link on npm ([7a85501](https://github.com/vikejs/vike-react/commit/7a85501148774c871a342881cbe9f06678378754))
+
+
+
+# 0.1.0 (2025-05-20)
+
+
+
diff --git a/packages/vike-react-zustand/README.md b/packages/vike-react-zustand/README.md
new file mode 100644
index 00000000..c0ec9d24
--- /dev/null
+++ b/packages/vike-react-zustand/README.md
@@ -0,0 +1,216 @@
+
+
+[](https://www.npmjs.com/package/vike-react-zustand)
+
+# `vike-react-zustand`
+
+Integrates [Zustand](https://zustand-demo.pmnd.rs/) state management into your [`vike-react`](https://vike.dev/vike-react) app with SSR support.
+
+> [!NOTE]
+> If you don't use any of your Zustand store during [Server-Side Rendering](https://vike.dev/ssr), then you **don't need `vike-react-zustand`** — you can just use Zustand directly without any Vike integration.
+>
+> The `vike-react-zustand` extension is about enabling you to use stores with SSR. See [How it works](#how-it-works).
+
+[Installation](#installation)
+[Basic usage](#basic-usage)
+[`withPageContext()`](#withpagecontext)
+[`useStoreVanilla()`](#usestorevanilla)
+[Example](#example)
+[Populate store with `+data`](#populate-store-with-data)
+[Version history](#version-history)
+[How it works](#how-it-works)
+[See also](#see-also)
+
+
+
+
+## Installation
+
+1. `npm install zustand vike-react-zustand`
+2. Extend `+config.js`:
+ ```js
+ // pages/+config.js
+
+ import vikeReact from 'vike-react/config'
+ import vikeReactZustand from 'vike-react-zustand/config'
+
+ export default {
+ // ...
+ extends: [vikeReact, vikeReactZustand]
+ }
+ ```
+
+> [!NOTE]
+> The `vike-react-zustand` extension requires [`vike-react`](https://vike.dev/vike-react).
+
+
+
+
+## Basic usage
+
+Create a store using the `create()` function from `vike-react-zustand`:
+
+```ts
+// store.ts
+
+import { create } from 'vike-react-zustand'
+
+interface Store {
+ counter: number
+ increment: () => void
+}
+
+export const useStore = create()((set) => ({
+ counter: 0,
+ increment: () => set((state) => ({ counter: state.counter + 1 })),
+}))
+```
+
+> [!NOTE]
+> The API is the same as [Zustand's `create()`](https://zustand.docs.pmnd.rs/apis/create#reference).
+>
+> (Extra parentheses `()` are required only when using TypeScript, as explained [here](https://zustand.docs.pmnd.rs/guides/typescript#basic-usage).)
+
+Use the store in your components:
+
+```jsx
+import { useStore } from './store'
+
+function Counter() {
+ const counter = useStore((state) => state.counter)
+ const increment = useStore((state) => state.increment)
+
+ return (
+
+ )
+}
+```
+
+
+
+## `withPageContext()`
+
+The `withPageContext` middleware gives your store access to the Vike [`pageContext`](https://vike.dev/pageContext) during initialization:
+
+```ts
+import { create, withPageContext } from 'vike-react-zustand'
+
+interface Store {
+ user: any
+ nodeVersion: string
+}
+
+export const useStore = create()(
+ withPageContext((pageContext) => (set, get, store) => ({
+ // Access pageContext data
+ user: pageContext.user
+ }))
+)
+```
+
+**API Reference**
+
+```ts
+const nextStateCreatorFn = withPageContext((pageContext) => stateCreatorFn)
+```
+
+- [`pageContext`](https://vike.dev/pageContext)
+- `stateCreatorFn`: A state creator function that takes `set` function, `get` function and `store` as arguments. Usually, you will return an object with the methods you want to expose.
+- Returns: a state creator function.
+
+
+
+## `useStoreVanilla()`
+
+Sometimes you need to access state in a non-reactive way or act upon the store. For these cases, you can use `useStoreVanilla` to directly access the [vanilla store](https://zustand.docs.pmnd.rs/apis/create-store).
+
+```tsx
+import { useStoreVanilla } from 'vike-react-zustand'
+import { useStore } from './store'
+
+function Component() {
+ const storeVanilla = useStoreVanilla(useStore)
+
+ // Subscribe to store changes
+ useEffect(
+ () => storeVanilla.subscribe(
+ state => console.log('Store changed:', state)
+ ),
+ []
+ )
+
+ // Get current state without subscribing to changes
+ const handleClick = () => {
+ const currentState = storeVanilla.getState()
+ storeVanilla.setState({ counter: currentState.counter + 5 })
+ }
+
+ return
+}
+```
+
+> [!NOTE]
+> Middlewares that modify `set` or `get` are not applied to `getState` and `setState`.
+
+
+
+## Example
+
+See [examples/zustand/](https://github.com/vikejs/vike-react/tree/main/examples/zustand).
+
+
+
+## Populate store with `+data`
+
+To populate your store with data fetched via the [`+data`](https://vike.dev/data) hook, use the [`withPageContext()`](#withpagecontext) middleware.
+
+```ts
+import { create, withPageContext } from 'vike-react-zustand'
+import { immer } from 'zustand/middleware/immer'
+import type { Data } from './+data'
+
+type Todo = { text: string }
+interface TodoStore {
+ todoItems: Todo[]
+ addTodo: (todo: Todo) => void
+}
+export const useTodoStore = create()(
+ withPageContext((pageContext) =>
+ immer((set, get) => ({
+ todoItems: (pageContext.data as Data).todoItemsInitial,
+ addTodo(todo) {
+ set((state) => {
+ state.todoItems.push(todo)
+ })
+ },
+ })),
+ ),
+)
+```
+
+See the To-Do List example at [examples/zustand/](https://github.com/vikejs/vike-react/tree/main/examples/zustand).
+
+
+
+## Version history
+
+See [CHANGELOG.md](https://github.com/vikejs/vike-react/blob/main/packages/vike-react-zustand/CHANGELOG.md).
+
+
+
+## How it works
+
+The `vike-react-zustand` extension enables Zustand stores to work seamlessly with [SSR](https://vike.dev/ssr):
+
+1. During SSR, store state is captured and serialized
+2. The serialized state is passed to the client
+3. On the client, the store is hydrated using the server-side state
+
+The extension handles all the complexities of state transfer between server and client, ensuring your React components have access to the same state during both serer-side rendering and client-side hydration.
+
+
+
+## See also
+
+- [Vike Docs > State Management](https://vike.dev/store)
+- [Zustand Documentation](https://docs.pmnd.rs/zustand)
diff --git a/packages/vike-react-zustand/package.json b/packages/vike-react-zustand/package.json
new file mode 100644
index 00000000..3c48a046
--- /dev/null
+++ b/packages/vike-react-zustand/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "vike-react-zustand",
+ "version": "0.1.5",
+ "homepage": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-zustand#readme",
+ "type": "module",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ ".": "./dist/index.js",
+ "./config": "./dist/integration/config.js"
+ },
+ "scripts": {
+ "dev": "tsc --watch",
+ "build": "rimraf dist/ && tsc",
+ "release": "release-me patch",
+ "release:minor": "release-me minor",
+ "release:commit": "release-me commit"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0",
+ "react-streaming": ">=0.3.42",
+ "vike-react": ">=0.4.13",
+ "zustand": ">=5.0.0"
+ },
+ "devDependencies": {
+ "@brillout/release-me": "^0.4.8",
+ "@types/babel__core": "^7.20.5",
+ "@types/node": "^24.0.8",
+ "@types/react": "^19.1.13",
+ "@types/react-dom": "^19.1.9",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "rimraf": "^5.0.5",
+ "typescript": "^5.9.2",
+ "vike": "^0.4.242",
+ "vike-react": "0.6.12",
+ "react-streaming": "^0.4.11",
+ "vite": "^7.1.7",
+ "zustand": "^5.0.3"
+ },
+ "dependencies": {
+ "@babel/core": "^7.24.0",
+ "@babel/types": "^7.24.0",
+ "@brillout/json-serializer": "^0.5.15"
+ },
+ "typesVersions": {
+ "*": {
+ "config": [
+ "dist/integration/config.d.ts"
+ ]
+ }
+ },
+ "files": [
+ "dist"
+ ],
+ "license": "MIT"
+}
diff --git a/packages/vike-react-zustand/src/context.ts b/packages/vike-react-zustand/src/context.ts
new file mode 100644
index 00000000..4d2ff08c
--- /dev/null
+++ b/packages/vike-react-zustand/src/context.ts
@@ -0,0 +1,17 @@
+export { setPageContext }
+export { getPageContext }
+
+import type { PageContext } from 'vike/types'
+import { getGlobalObject } from './utils/getGlobalObject.js'
+
+const globalObject = getGlobalObject('context.ts', {
+ pageContextCurrent: null as PageContext | null,
+})
+
+function setPageContext(pageContext: PageContext | null) {
+ globalObject.pageContextCurrent = pageContext
+}
+
+function getPageContext() {
+ return globalObject.pageContextCurrent
+}
diff --git a/packages/vike-react-zustand/src/getOrCreateStore.ts b/packages/vike-react-zustand/src/getOrCreateStore.ts
new file mode 100644
index 00000000..5d9baf16
--- /dev/null
+++ b/packages/vike-react-zustand/src/getOrCreateStore.ts
@@ -0,0 +1,84 @@
+export { getOrCreateStore }
+export type { CreateStoreReturn }
+
+import { parse } from '@brillout/json-serializer/parse'
+import { stringify } from '@brillout/json-serializer/stringify'
+import type { PageContext } from 'vike/types'
+import { create as createZustand, StateCreator } from 'zustand'
+import { setPageContext } from './context.js'
+import { assert } from './utils/assert.js'
+import { getGlobalObject } from './utils/getGlobalObject.js'
+import { sanitizeForSerialization } from './utils/sanitizeForSerialization.js'
+import { assignDeep } from './utils/assignDeep.js'
+
+// Client-side cache (not used in SSR)
+const clientCache = import.meta.env.SSR
+ ? null
+ : getGlobalObject('getOrCreateStore.ts', {
+ initializers: {} as Record>,
+ stores: {} as Record>,
+ })
+
+function getOrCreateStore({
+ key,
+ initializerFn,
+ pageContext,
+ stream,
+}: {
+ key: string
+ initializerFn: StateCreator
+ pageContext: PageContext
+ stream: ReturnType
+}): CreateStoreReturn {
+ try {
+ setPageContext(pageContext)
+ if (import.meta.env.SSR) {
+ pageContext._vikeReactZustandStoresServer ??= {}
+ let store = pageContext._vikeReactZustandStoresServer[key] as CreateStoreReturn
+ if (store) return store
+ store = createStore_(initializerFn)
+ const serverState = store.getInitialState()
+ const transferableState = sanitizeForSerialization(serverState)
+ assert(stream)
+ stream.injectToStream(
+ ``,
+ )
+ pageContext._vikeReactZustandStoresServer[key] = store
+ return store
+ } else {
+ assert(clientCache)
+ const storeNeedsRecreate = clientCache.initializers[key] !== initializerFn
+ if (storeNeedsRecreate) {
+ const store = createStore_(initializerFn)
+ clientCache.stores[key] = store
+ clientCache.initializers[key] = initializerFn
+ assignServerStateOptional({ key, store })
+ return store
+ } else {
+ const store = clientCache.stores[key]
+ assert(store)
+ return store
+ }
+ }
+ } finally {
+ setPageContext(null)
+ }
+}
+
+type CreateStoreReturn = ReturnType>
+function createStore_(initializer: StateCreator) {
+ return createZustand()(initializer)
+}
+
+declare global {
+ var _vikeReactZustandState: undefined | Record
+}
+function assignServerStateOptional({ key, store }: { key: string; store: CreateStoreReturn }) {
+ if (globalThis._vikeReactZustandState && globalThis._vikeReactZustandState[key]) {
+ const clientState = store.getInitialState()
+ const serverState = parse(globalThis._vikeReactZustandState[key])
+ assert(clientState && typeof clientState === 'object')
+ assert(serverState && typeof serverState === 'object')
+ assignDeep(clientState, serverState)
+ }
+}
diff --git a/packages/vike-react-zustand/src/index.ts b/packages/vike-react-zustand/src/index.ts
new file mode 100644
index 00000000..75488f03
--- /dev/null
+++ b/packages/vike-react-zustand/src/index.ts
@@ -0,0 +1,109 @@
+export { withPageContext } from './withPageContext.js'
+export { createWrapped as create, useStoreVanilla }
+
+import { useStreamOptional } from 'react-streaming'
+import { usePageContext } from 'vike-react/usePageContext'
+import type { StateCreator } from 'zustand'
+import { getOrCreateStore } from './getOrCreateStore.js'
+import type { Create, StoreVanillaAndHook, StoreVanilla, StoreHookOnly } from './types.js'
+import { assert } from './utils/assert.js'
+
+// Define Symbol keys for internal use
+const STORE_KEY = Symbol.for('vike-react-zustand-store-key')
+const STORE_INITIALIZER_FN = Symbol.for('vike-react-zustand-store-initializer-fn')
+
+// Internal interface that extends StoreHookOnly with our symbol properties
+// This keeps the symbols out of the public API while allowing TypeScript to type-check correctly
+interface InternalStoreHookOnly extends StoreHookOnly {
+ [STORE_KEY]: string
+ [STORE_INITIALIZER_FN]: StateCreator
+}
+
+/**
+ * Same API as Zustand's `create()`:
+ *
+ * `const useSomeStore = create(stateCreatorFn)`
+ *
+ * https://github.com/vikejs/vike-react/tree/main/packages/vike-react-zustand
+ */
+const createWrapped = ((...args: any[]) => {
+ const initializerFn =
+ // create('key', (set,get) => ...)
+ // ^^^^^^^^^^^^^^^
+ (typeof args[1] === 'function' && args[1]) ||
+ // The transform didn't run for this call(skipped in node_modules)
+ // create((set,get) => ...)
+ // ^^^^^^^^^^^^^^^
+ (typeof args[0] === 'function' && args[0]) ||
+ // create('key')((set,get) => ...)
+ // ^^^
+ // create()((set,get) => ...)
+ // ^
+ undefined
+
+ const key =
+ // create('key')((set,get) => ...)
+ // ^^^
+ // create('key', (set,get) => ...)
+ // ^^^
+ (typeof args[0] === 'string' && args[0]) ||
+ // The transform didn't run for this call(skipped in node_modules)
+ // create()((set,get) => ...)
+ // create((set,get) => ...)
+ 'default'
+
+ assert(key)
+
+ const create_ = (initializerFn_: StateCreator): StoreHookOnly => {
+ assert(initializerFn_)
+ const useStore = ((...args: Parameters>) => {
+ const store = useStoreVanilla(useStore) as StoreVanillaAndHook
+ return store(...args)
+ }) as InternalStoreHookOnly
+
+ useStore[STORE_KEY] = key
+ useStore[STORE_INITIALIZER_FN] = initializerFn_
+ return useStore
+ }
+
+ if (initializerFn) {
+ // create((set,get) => ...)
+ return create_(initializerFn)
+ }
+
+ // create()((set,get) => ...)
+ return create_
+}) as Create
+
+/**
+ * Sometimes you need to access state in a non-reactive way or act upon the store. For these cases, you can use `useStoreVanilla` to directly access the vanilla store.
+ *
+ * ```ts
+ * import { useStoreVanilla } from 'vike-react-zustand'
+ * import { useStore } from './store'
+ *
+ * function Component() {
+ * const storeVanilla = useStoreVanilla(useStore)
+ * function onClick() {
+ * storeVanilla.setState({ ... })
+ * }
+ * }
+ * ```
+ *
+ * ⚠️ Note that middlewares that modify set or get are not applied to `getState` and `setState`.
+ *
+ *
+ * https://github.com/vikejs/vike-react/tree/main/packages/vike-react-zustand
+ */
+function useStoreVanilla(useStore: StoreHookOnly): StoreVanilla {
+ const internalStoreHook = useStore as InternalStoreHookOnly
+ const key = internalStoreHook[STORE_KEY]
+ const initializerFn = internalStoreHook[STORE_INITIALIZER_FN]
+ assert(key)
+ assert(initializerFn)
+ const pageContext = usePageContext()
+ const stream = useStreamOptional()
+ const store = getOrCreateStore({ key, initializerFn, pageContext, stream })
+ assert(store)
+ return store as StoreVanilla
+}
diff --git a/packages/vike-react-zustand/src/integration/config.ts b/packages/vike-react-zustand/src/integration/config.ts
new file mode 100644
index 00000000..734d3968
--- /dev/null
+++ b/packages/vike-react-zustand/src/integration/config.ts
@@ -0,0 +1,14 @@
+import type { Config } from 'vike/types'
+import { vikeReactZustand } from '../plugin/index.js'
+import 'vike-react/config' // Needed for merging vike-react's Vike.Config such as +stream
+
+export default {
+ name: 'vike-react-zustand',
+ require: {
+ 'vike-react': '>=0.4.13',
+ },
+ stream: { require: true },
+ vite: {
+ plugins: [vikeReactZustand()],
+ },
+} satisfies Config
diff --git a/packages/vike-react-zustand/src/integration/types.d.ts b/packages/vike-react-zustand/src/integration/types.d.ts
new file mode 100644
index 00000000..7a966e65
--- /dev/null
+++ b/packages/vike-react-zustand/src/integration/types.d.ts
@@ -0,0 +1,11 @@
+export type {}
+
+// The types we add here aren't visible to the user (because this file only matches the TypeScript rootDir of packages/vike-react-zustand/tsconfig.json)
+
+declare global {
+ namespace Vike {
+ interface PageContext {
+ _vikeReactZustandStoresServer: { [key: string]: import('../getOrCreateStore.ts').CreateStoreReturn }
+ }
+ }
+}
diff --git a/packages/vike-react-zustand/src/plugin/babelTransformer.ts b/packages/vike-react-zustand/src/plugin/babelTransformer.ts
new file mode 100644
index 00000000..994be542
--- /dev/null
+++ b/packages/vike-react-zustand/src/plugin/babelTransformer.ts
@@ -0,0 +1,133 @@
+import { transformAsync, type PluginItem } from '@babel/core'
+import * as t from '@babel/types'
+
+type TransformResult = {
+ code: string
+ map: any
+} | null
+
+type State = {
+ modified: boolean
+ hasVikeReactZustand: boolean
+ storeKeyCounter: number
+ createNames: Set
+ isStoreFile: boolean
+}
+
+export async function transformCode(code: string, id: string): Promise {
+ try {
+ const state: State = {
+ modified: false,
+ hasVikeReactZustand: false,
+ storeKeyCounter: 0,
+ createNames: new Set(),
+ isStoreFile: false,
+ }
+
+ const result = await transformAsync(code, {
+ filename: id,
+ ast: true,
+ sourceMaps: true,
+ plugins: [
+ // Plugin to analyze imports and track local names
+ analyzeImportsPlugin(state),
+ // Plugin to add keys to create() calls
+ addStoreKeysPlugin(state, id),
+ ].filter(Boolean) as PluginItem[],
+ })
+
+ if (!result?.code || !state.hasVikeReactZustand || !state.modified) {
+ return null
+ }
+
+ // Add HMR code to store files
+ let finalCode = result.code
+ if (state.isStoreFile) {
+ const hmrCode = `
+// HMR for store
+if (import.meta.hot) {
+ import.meta.hot.accept(() => {
+ window.location.reload()
+ })
+}`
+ finalCode += hmrCode
+ }
+
+ return {
+ code: finalCode,
+ map: result.map,
+ }
+ } catch (error) {
+ console.error(`Error transforming code from ${id}:`, error)
+ return null
+ }
+}
+
+/**
+ * Plugin to analyze imports and track local names
+ */
+function analyzeImportsPlugin(state: State): PluginItem {
+ return {
+ visitor: {
+ ImportDeclaration(path) {
+ if (path.node.source.value !== 'vike-react-zustand') {
+ return
+ }
+ state.hasVikeReactZustand = true
+
+ // Process import specifiers
+ for (const specifier of path.node.specifiers) {
+ if (t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported)) {
+ const importedName = specifier.imported.name
+ const localName = specifier.local.name
+
+ if (importedName === 'create') {
+ state.createNames.add(localName)
+ }
+ }
+ }
+ },
+ },
+ }
+}
+
+/**
+ * Plugin to add keys to create() calls
+ */
+function addStoreKeysPlugin(state: State, moduleId: string): PluginItem {
+ return {
+ visitor: {
+ CallExpression(path) {
+ if (!t.isIdentifier(path.node.callee) || !state.createNames.has(path.node.callee.name)) {
+ return
+ }
+
+ // Mark this as a store file for HMR plugin
+ state.isStoreFile = true
+
+ // Skip if first argument is already a string literal
+ if (path.node.arguments.length > 0 && t.isStringLiteral(path.node.arguments[0])) {
+ return
+ }
+
+ // Generate a unique key
+ const key = simpleHash(`${moduleId}:${state.storeKeyCounter++}`)
+
+ // Add the key as the first argument
+ path.node.arguments.unshift(t.stringLiteral(key))
+ state.modified = true
+ },
+ },
+ }
+}
+
+/**
+ * Simple hash function for generating store keys
+ */
+function simpleHash(str: string): string {
+ let hash = 0
+ for (let i = 0; i < str.length; i++) {
+ hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0
+ }
+ return (hash >>> 0).toString(36)
+}
diff --git a/packages/vike-react-zustand/src/plugin/index.ts b/packages/vike-react-zustand/src/plugin/index.ts
new file mode 100644
index 00000000..f8dd9c00
--- /dev/null
+++ b/packages/vike-react-zustand/src/plugin/index.ts
@@ -0,0 +1,50 @@
+export { vikeReactZustand }
+
+import type { Plugin } from 'vite'
+import { transformCode } from './babelTransformer.js'
+import { assert } from '../utils/assert.js'
+
+const skipNonJsFiles = /\.[jt]sx?$/
+const skipNodeModules = 'node_modules'
+const filterRolldown = {
+ id: {
+ include: skipNonJsFiles,
+ exclude: `**/${skipNodeModules}/**`,
+ },
+}
+const filterFunction = (id: string) => {
+ if (id.includes(skipNodeModules)) return false
+ if (!skipNonJsFiles.test(id)) return false
+ return true
+}
+
+type PluginInterop = Record & { name: string }
+// Return `PluginInterop` instead of `Plugin` to avoid type mismatch upon different Vite versions
+function vikeReactZustand(): PluginInterop[] {
+ const plugins: Plugin[] = [
+ {
+ name: 'vike-react-zustand:config',
+ configEnvironment: {
+ handler() {
+ return {
+ resolve: {
+ noExternal: ['vike-react-zustand'],
+ },
+ }
+ },
+ },
+ },
+ {
+ name: 'vike-react-zustand:transform',
+ enforce: 'post',
+ transform: {
+ filter: filterRolldown,
+ handler(code, id) {
+ assert(filterFunction(id))
+ return transformCode(code, id)
+ },
+ },
+ },
+ ]
+ return plugins as any
+}
diff --git a/packages/vike-react-zustand/src/types.ts b/packages/vike-react-zustand/src/types.ts
new file mode 100644
index 00000000..422b1e76
--- /dev/null
+++ b/packages/vike-react-zustand/src/types.ts
@@ -0,0 +1,48 @@
+export type { StoreVanillaAndHook, StoreVanilla, StoreHookOnly, Create }
+
+import type { StateCreator, StoreApi, StoreMutatorIdentifier } from 'zustand'
+
+/**
+ * The store hook function that is returned by createWrapped
+ */
+type StoreHookOnly = {
+ (): T
+ (selector: (state: T) => U): U
+}
+
+/**
+ * Just the store API without the hook functionality
+ */
+type StoreVanilla = StoreApi
+
+/**
+ * Combined type used in the React context
+ */
+type StoreVanillaAndHook = StoreVanilla & {
+ (): any
+ (selector: (state: any) => U): U
+}
+
+/**
+ * The create function type with support for the key parameter
+ */
+type Create = {
+ // Direct call with initializer
+ (initializer: StateCreator): StoreHookOnly
+
+ // Direct call with key and initializer
+ (
+ key: string,
+ initializer: StateCreator,
+ ): StoreHookOnly
+
+ // Curried call with no arguments
+ (): (
+ initializer: StateCreator,
+ ) => StoreHookOnly
+
+ // Curried call with key
+ (
+ key: string,
+ ): (initializer: StateCreator) => StoreHookOnly
+}
diff --git a/packages/vike-react-zustand/src/utils/assert.ts b/packages/vike-react-zustand/src/utils/assert.ts
new file mode 100644
index 00000000..c1772ad1
--- /dev/null
+++ b/packages/vike-react-zustand/src/utils/assert.ts
@@ -0,0 +1,13 @@
+export { assert, assertUsage }
+
+function assert(condition: unknown): asserts condition {
+ if (condition) return
+ throw new Error(
+ "You stumbled upon a bug in vike-react-zustand's source code. Reach out on GitHub and we will fix the bug.",
+ )
+}
+
+function assertUsage(condition: unknown, message: string): asserts condition {
+ if (condition) return
+ throw new Error('Wrong usage: ' + message)
+}
diff --git a/packages/vike-react-zustand/src/utils/assignDeep.ts b/packages/vike-react-zustand/src/utils/assignDeep.ts
new file mode 100644
index 00000000..ce3f340c
--- /dev/null
+++ b/packages/vike-react-zustand/src/utils/assignDeep.ts
@@ -0,0 +1,32 @@
+// Credits: https://github.com/radashi-org/radashi/blob/main/src/object/assign.ts
+
+export { assignDeep }
+
+function assignDeep(initial: Record, override: Record) {
+ if (!initial || !override) {
+ return initial ?? override ?? {}
+ }
+ for (const key of Object.keys(override)) {
+ initial[key] =
+ isPlainObject(initial[key]) && isPlainObject(override[key])
+ ? assignDeep(initial[key], override[key])
+ : override[key]
+ }
+ return initial
+}
+
+function isPlainObject(value: any): value is object {
+ if (typeof value !== 'object' || value === null) {
+ return false
+ }
+
+ const prototype = Object.getPrototypeOf(value)
+ return (
+ // Fast path for most common objects.
+ prototype === Object.prototype ||
+ // Support objects created without a prototype.
+ prototype === null ||
+ // Support plain objects from other realms.
+ Object.getPrototypeOf(prototype) === null
+ )
+}
diff --git a/packages/vike-react-zustand/src/utils/getGlobalObject.ts b/packages/vike-react-zustand/src/utils/getGlobalObject.ts
new file mode 100644
index 00000000..c01e5b01
--- /dev/null
+++ b/packages/vike-react-zustand/src/utils/getGlobalObject.ts
@@ -0,0 +1,12 @@
+export function getGlobalObject = never>(
+ // We use the filename as key; each `getGlobalObject()` call should live in a unique filename.
+ key: `${string}.ts`,
+ defaultValue: T,
+): T {
+ const allGlobalObjects = (globalThis.__vike_react_zustand = globalThis.__vike_react_zustand || {})
+ const globalObject = (allGlobalObjects[key] = (allGlobalObjects[key] as T) || defaultValue)
+ return globalObject
+}
+declare global {
+ var __vike_react_zustand: undefined | Record>
+}
diff --git a/packages/vike-react-zustand/src/utils/sanitizeForSerialization.ts b/packages/vike-react-zustand/src/utils/sanitizeForSerialization.ts
new file mode 100644
index 00000000..7490ca6c
--- /dev/null
+++ b/packages/vike-react-zustand/src/utils/sanitizeForSerialization.ts
@@ -0,0 +1,100 @@
+export { sanitizeForSerialization }
+
+/**
+ * Sanitizes data for serialization by removing functions, promises, and undefined values.
+ * Creates a deep copy of the input with all non-serializable values removed.
+ */
+function sanitizeForSerialization(input: T, visited = new WeakSet