├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── blank.yml
│ └── config.yml
└── workflows
│ ├── ci.yml
│ ├── discord.yml
│ └── formatting.yml
├── .gitignore
├── .npmrc
├── .prettierrc
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── biome.json
├── examples
├── README.md
├── apollo
│ ├── .gitignore
│ ├── .test-dev.test.ts
│ ├── .test-preview.test.ts
│ ├── .testRun.ts
│ ├── README.md
│ ├── assets
│ │ └── logo.svg
│ ├── layouts
│ │ ├── HeadDefault.tsx
│ │ ├── LayoutDefault.tsx
│ │ └── style.css
│ ├── package.json
│ ├── pages
│ │ ├── +ApolloClient.ts
│ │ ├── +config.ts
│ │ ├── _error
│ │ │ └── +Page.tsx
│ │ └── index
│ │ │ ├── +Page.tsx
│ │ │ ├── Counter.tsx
│ │ │ └── Countries.tsx
│ ├── tsconfig.json
│ └── vite.config.ts
├── full
│ ├── .gitignore
│ ├── .test-dev.test.ts
│ ├── .test-preview.test.ts
│ ├── .testRun.ts
│ ├── README.md
│ ├── assets
│ │ ├── logo-new.svg
│ │ └── logo.svg
│ ├── components
│ │ ├── ClientOnlyComponent.tsx
│ │ ├── Counter.tsx
│ │ └── Link.tsx
│ ├── package.json
│ ├── pages
│ │ ├── +Head.tsx
│ │ ├── +Layout.tsx
│ │ ├── +config.ts
│ │ ├── +react.client.ts
│ │ ├── +react.server.ts
│ │ ├── _error
│ │ │ └── +Page.tsx
│ │ ├── client-only
│ │ │ └── +Page.tsx
│ │ ├── images
│ │ │ └── +Page.tsx
│ │ ├── index
│ │ │ ├── +Page.tsx
│ │ │ └── +config.ts
│ │ ├── star-wars
│ │ │ ├── @id
│ │ │ │ ├── +Page.tsx
│ │ │ │ └── +data.tsx
│ │ │ ├── index
│ │ │ │ ├── +Page.tsx
│ │ │ │ └── +data.ts
│ │ │ └── types.ts
│ │ ├── starship
│ │ │ ├── +Layout.tsx
│ │ │ ├── +Page.tsx
│ │ │ ├── +config.ts
│ │ │ ├── reviews
│ │ │ │ └── +Page.tsx
│ │ │ └── spec
│ │ │ │ └── +Page.tsx
│ │ ├── streaming
│ │ │ └── +Page.tsx
│ │ ├── style.css
│ │ └── without-ssr
│ │ │ ├── +Page.tsx
│ │ │ └── +config.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── minimal
│ ├── .gitignore
│ ├── .test-dev.test.ts
│ ├── .test-preview.test.ts
│ ├── .testRun.ts
│ ├── README.md
│ ├── package.json
│ ├── pages
│ │ ├── +config.js
│ │ ├── Layout.css
│ │ ├── Layout.jsx
│ │ ├── about
│ │ │ └── +Page.jsx
│ │ └── index
│ │ │ ├── +Page.jsx
│ │ │ └── Counter.jsx
│ └── vite.config.js
├── query
│ ├── .gitignore
│ ├── .test-dev.test.ts
│ ├── .test-preview.test.ts
│ ├── .testRun.ts
│ ├── README.md
│ ├── assets
│ │ └── logo.svg
│ ├── layouts
│ │ ├── HeadDefault.tsx
│ │ ├── LayoutDefault.tsx
│ │ └── style.css
│ ├── package.json
│ ├── pages
│ │ ├── +config.ts
│ │ └── index
│ │ │ ├── +Page.tsx
│ │ │ ├── @id
│ │ │ ├── +Page.tsx
│ │ │ └── Movie.tsx
│ │ │ ├── Counter.tsx
│ │ │ ├── Movies.tsx
│ │ │ └── types.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── redux
│ ├── .gitignore
│ ├── .test-dev.test.ts
│ ├── .test-preview-ssg.test.ts
│ ├── .test-preview.test.ts
│ ├── .testRun.ts
│ ├── README.md
│ ├── components
│ │ ├── Counter.tsx
│ │ ├── Counter
│ │ │ └── fetchCountInit.ts
│ │ └── Link.tsx
│ ├── layouts
│ │ ├── LayoutDefault.tsx
│ │ ├── logo.svg
│ │ └── style.css
│ ├── package.json
│ ├── pages
│ │ ├── +config.ts
│ │ ├── +redux.ts
│ │ ├── about
│ │ │ ├── +Page.tsx
│ │ │ ├── +config.ts
│ │ │ ├── +data.ts
│ │ │ └── +onData.ts
│ │ └── index
│ │ │ ├── +Page.tsx
│ │ │ ├── +data.ts
│ │ │ ├── +onData.ts
│ │ │ └── TodoList.tsx
│ ├── store
│ │ ├── createStore.ts
│ │ ├── hooks.ts
│ │ └── slices
│ │ │ ├── count.ts
│ │ │ └── todos.ts
│ ├── tsconfig.json
│ └── vite.config.ts
└── zustand
│ ├── .gitignore
│ ├── .test-dev.test.ts
│ ├── .test-preview.test.ts
│ ├── .testRun.ts
│ ├── README.md
│ ├── assets
│ └── logo.svg
│ ├── components
│ └── Counter.tsx
│ ├── layouts
│ ├── HeadDefault.tsx
│ ├── LayoutDefault.tsx
│ └── style.css
│ ├── package.json
│ ├── pages
│ ├── +Layout.tsx
│ ├── +config.ts
│ ├── _error
│ │ └── +Page.tsx
│ ├── about
│ │ └── +Page.tsx
│ └── index
│ │ ├── +Page.tsx
│ │ ├── +data.ts
│ │ └── TodoList.tsx
│ ├── store.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── package.json
├── packages
├── vike-react-antd
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── Wrapper.server.tsx
│ ├── config.ts
│ ├── onAfterRenderHtml.ts
│ ├── onBeforeRenderHtml.ts
│ ├── package.json
│ └── tsconfig.json
├── vike-react-apollo
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── integration
│ │ │ ├── +config.ts
│ │ │ ├── Transport.tsx
│ │ │ └── Wrapper.tsx
│ │ ├── utils
│ │ │ └── assert.ts
│ │ └── withFallback.tsx
│ └── tsconfig.json
├── vike-react-chakra
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── Wrapper.tsx
│ ├── config.ts
│ ├── eject.config.js
│ ├── package.json
│ └── tsconfig.json
├── vike-react-query
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── integration
│ │ │ ├── +config.ts
│ │ │ ├── FallbackErrorBoundary.tsx
│ │ │ ├── StreamedHydration.tsx
│ │ │ └── Wrapper.tsx
│ │ ├── withFallback.spec.tsx
│ │ └── withFallback.tsx
│ ├── tsconfig.json
│ └── vitest.config.ts
├── vike-react-redux
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── Wrapper.tsx
│ ├── config.ts
│ ├── onAfterRenderHtml.server.ts
│ ├── onBeforeRenderClient.client.ts
│ ├── onCreatePageContext.server.ts
│ ├── package.json
│ └── tsconfig.json
├── vike-react-styled-components
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── Wrapper.server.tsx
│ ├── config.ts
│ ├── onAfterRenderHtml.ts
│ ├── onBeforeRenderHtml.ts
│ ├── package.json
│ └── tsconfig.json
├── vike-react-styled-jsx
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── Wrapper.server.tsx
│ ├── config.ts
│ ├── onAfterRenderHtml.ts
│ ├── onBeforeRenderHtml.ts
│ ├── package.json
│ └── tsconfig.json
├── vike-react-zustand
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── context.ts
│ │ ├── getOrCreateStore.ts
│ │ ├── index.ts
│ │ ├── integration
│ │ │ ├── config.ts
│ │ │ └── types.d.ts
│ │ ├── plugin
│ │ │ ├── babelTransformer.ts
│ │ │ └── index.ts
│ │ ├── types.ts
│ │ ├── utils
│ │ │ ├── assert.ts
│ │ │ ├── assignDeep.ts
│ │ │ ├── getGlobalObject.ts
│ │ │ └── sanitizeForSerialization.ts
│ │ └── withPageContext.ts
│ └── tsconfig.json
└── vike-react
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── CONTRIBUTING.md
│ ├── README.md
│ ├── eject.config.js
│ ├── package.json
│ ├── src
│ ├── components
│ │ ├── ClientOnly.tsx
│ │ ├── Config
│ │ │ ├── Config-client.ts
│ │ │ └── Config-server.ts
│ │ └── Head
│ │ │ ├── Head-client.ts
│ │ │ └── Head-server.ts
│ ├── config.ts
│ ├── helpers
│ │ └── clientOnly.tsx
│ ├── hooks
│ │ ├── useConfig
│ │ │ ├── configsCumulative.ts
│ │ │ ├── useConfig-client.ts
│ │ │ └── useConfig-server.ts
│ │ ├── useData.tsx
│ │ └── usePageContext.tsx
│ ├── index.ts
│ ├── integration
│ │ ├── Loading.css
│ │ ├── Loading.tsx
│ │ ├── applyHeadSettings.tsx
│ │ ├── getHeadSetting.tsx
│ │ ├── getPageElement.tsx
│ │ ├── onRenderClient.tsx
│ │ ├── onRenderHtml.tsx
│ │ ├── resolveReactOptions.ts
│ │ └── ssrEffect.ts
│ ├── types
│ │ ├── Config.ts
│ │ └── PageContext.ts
│ └── utils
│ │ ├── assert.ts
│ │ ├── callCumulativeHooks.ts
│ │ ├── getGlobalObject.ts
│ │ ├── getTagAttributesString.ts
│ │ ├── includes.ts
│ │ ├── isCallable.ts
│ │ ├── isNotFalse.ts
│ │ ├── isNotNullish.ts
│ │ ├── isObject.ts
│ │ ├── isReactElement.ts
│ │ ├── isType.ts
│ │ ├── objectEntries.ts
│ │ └── objectKeys.ts
│ └── tsconfig.json
├── pnpm-lock.yaml
└── pnpm-workspace.yaml
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: vikejs
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/blank.yml:
--------------------------------------------------------------------------------
1 | name: "Blank issue"
2 | description: Don't use this unless you are a Vike maintainer.
3 | body:
4 | - type: markdown
5 | attributes:
6 | value: |
7 | # **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.
8 | - type: textarea
9 | attributes:
10 | label: Description
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: "💥 Bug"
4 | url: https://github.com/vikejs/vike/issues/new?template=bug.yml
5 | about: "Report a bug. (Redirects to Vike's main repository.)"
6 | - name: "🚀 Feature"
7 | url: https://github.com/vikejs/vike/issues/new?template=feature.yml
8 | about: "Suggest a new feature. (Redirects to Vike's main repository.)"
9 | - name: "✨ Polish"
10 | url: https://github.com/vikejs/vike/issues/new?template=polish.yaml
11 | about: "Unclear API or docs? Let us know — we'll polish Vike's DX. (Redirects to Vike's main repository.)"
12 | - name: "🙏 Help & Questions"
13 | url: https://github.com/vikejs/vike/discussions/new?category=help-questions
14 | about: "Get official help from Vike maintainers."
15 | - name: "🗨️ Other"
16 | url: https://github.com/vikejs/vike/discussions/new?category=other
17 | about: "Anything else: ideas, feedback, complaints, appreciation, ..."
18 | - name: "💬 Chit-chat"
19 | url: https://discord.com/invite/hfHhnJyVg8
20 | about: "Casual conversation with the community."
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [ push, pull_request ]
3 |
4 | jobs:
5 | test:
6 | # Prevent workflow being run twice, https://github.com/orgs/community/discussions/57827#discussioncomment-6579237
7 | if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
8 |
9 | name: ${{ matrix.cmd }} (${{ matrix.os }})
10 |
11 | runs-on: ${{ matrix.os }}
12 |
13 | strategy:
14 | # Don't cancel other matrix operations if one fails
15 | fail-fast: false
16 | matrix:
17 | os:
18 | - ubuntu-latest
19 | - windows-latest
20 | cmd:
21 | - pnpm run test:e2e
22 | - pnpm run test:units
23 | - pnpm run test:types
24 | # `exclude` docs & alternatives:
25 | # - https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrixexclude
26 | # - https://stackoverflow.com/questions/68994484/how-to-skip-a-configuration-of-a-matrix-with-github-actions/68994907#68994907
27 | # - https://stackoverflow.com/questions/66025220/paired-values-in-github-actions-matrix/68940067#68940067
28 | exclude:
29 | - os: windows-latest
30 | cmd: pnpm run test:types
31 |
32 | steps:
33 | - uses: actions/checkout@v4
34 | - uses: pnpm/action-setup@v4
35 | - uses: actions/setup-node@v4
36 | with:
37 | node-version: 20
38 | # TODO/eventually: try using the cache again
39 | # The cache breaks playwright installation, see https://github.com/vikejs/vike-vue/pull/119
40 | # cache: "pnpm"
41 |
42 | - run: pnpm install
43 | - run: pnpm exec playwright install chromium
44 | - run: pnpm run build
45 |
46 | - run: ${{ matrix.cmd }}
47 |
--------------------------------------------------------------------------------
/.github/workflows/discord.yml:
--------------------------------------------------------------------------------
1 | name: Discord Notification
2 |
3 | on:
4 | workflow_run:
5 | workflows: ['CI', 'Docs']
6 | types: [completed]
7 |
8 | jobs:
9 | notify:
10 | # 'skipped' means the `if: ` condition wasn't fulfilled
11 | if: ${{ github.event.workflow_run.conclusion != 'skipped' }}
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Push the notification using Discord webhook
15 | env:
16 | # 🟢 has poor compatibility: https://www.unicompat.com/1F7E2
17 | CI_STATUS: ${{ github.event.workflow_run.conclusion == 'success' && '✅' || github.event.workflow_run.conclusion == 'failure' && '🔴' || github.event.workflow_run.conclusion }}
18 | REPO_NAME: ${{ github.event.repository.name }}
19 | GIT_BRANCH: ${{ github.event.workflow_run.head_branch }}
20 | WORKFLOW_NAME: ${{ github.event.workflow_run.name }}
21 | WORKFLOW_URL: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.html_url || '' }}
22 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
23 | # Playground: https://gist.github.com/brillout/da1ab713bda6e3c0d2ad909c1a4e80f2
24 | run: |
25 | curl \
26 | -i \
27 | -H "Accept: application/json" \
28 | -H "Content-Type:application/json" \
29 | -X POST \
30 | --data "{\"content\":\"$CI_STATUS $REPO_NAME $WORKFLOW_NAME $GIT_BRANCH $WORKFLOW_URL\",\"username\":\"GitHub\",\"avatar_url\":\"https://i.imgur.com/OOtUMJD.png\"}" \
31 | $DISCORD_WEBHOOK
32 | - name: Debug/inspect
33 | env:
34 | EVENT: ${{ toJSON( github.event ) }}
35 | run: echo $EVENT
36 |
--------------------------------------------------------------------------------
/.github/workflows/formatting.yml:
--------------------------------------------------------------------------------
1 | name: 'Check formatting'
2 | on:
3 | push:
4 | jobs:
5 | check_formatting:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@v4
9 | - uses: pnpm/action-setup@v4
10 | - run: pnpm install
11 | - run: pnpm run format:check
12 |
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | .pnpm-debug.log
3 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | link-workspace-packages=deep
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | semi: false
2 | tabWidth: 2
3 | singleQuote: true
4 | printWidth: 120
5 | trailingComma: all
6 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributions welcome!
2 |
3 | **1. Install**
4 |
5 | ```shell
6 | git clone git@github.com:vikejs/vike-react
7 | cd vike-react/
8 | pnpm install
9 | ```
10 |
11 | > [!NOTE]
12 | > See [System requirements](#system-requirements) for how to install pnpm.
13 |
14 | **2. Build**
15 |
16 | Build all `packages/*`:
17 |
18 | ```shell
19 | pnpm build
20 | ```
21 |
22 | **3. Develop**
23 |
24 | Watch & re-build upon modifications:
25 |
26 | ```shell
27 | cd packages/vike-react # or any other packages/*
28 | pnpm dev
29 | ```
30 |
31 | In a second shell:
32 |
33 | ```shell
34 | cd examples/full/ # or any other examples/*
35 | pnpm dev
36 | ```
37 |
38 | That's it. You can now test your modifications.
39 |
40 | > [!WARNING]
41 | > After changing the source code of `vike-react(-*)`, make sure to **always clear Vite's client cache** with `$ rm -rf examples/full/node_modules/.vite/`. Otherwise you'll get a version mismatch between the server (using the latest build) and the client (using the previous cached build).
42 |
43 | > [!WARNING]
44 | > When switching Git branches, make sure to **run `pnpm reset`** at the monorepo root: it will re-install and re-build everything. It's required when switching to a branch that, for example, requires another Vike version.
45 |
46 |
47 |
48 | ## System requirements
49 |
50 | - Node.js `>=16.0.0`
51 | - pnpm `>=9.0.0`
52 |
53 | > [!NOTE]
54 | > To install [pnpm](https://pnpm.io) run:
55 | > ```shell
56 | > npm install -g pnpm
57 | > ```
58 | > (Or see [pnpm Docs > Installation](https://pnpm.io/installation) for alternative methods.)
59 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023-present Romuald Brillout
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Source code of following Vike React extensions: [Vike Docs > Extensions > React](https://vike.dev/extensions#react).
2 |
3 | See [packages/](packages/).
4 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3 | "files": {
4 | "ignore": ["dist/", "package.json"]
5 | },
6 | "formatter": {
7 | "indentWidth": 2,
8 | "indentStyle": "space"
9 | },
10 | "javascript": {
11 | "formatter": {
12 | "semicolons": "asNeeded",
13 | "lineWidth": 120,
14 | "quoteStyle": "single",
15 | "trailingCommas": "all"
16 | }
17 | },
18 | "linter": {
19 | "enabled": false
20 | },
21 | "vcs": {
22 | "enabled": true,
23 | "clientKind": "git"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | > [!NOTE]
2 | > For more examples, see [Bati](https://batijs.dev) which generates `vike-react` apps.
3 |
--------------------------------------------------------------------------------
/examples/apollo/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/examples/apollo/.test-dev.test.ts:
--------------------------------------------------------------------------------
1 | import { testRun } from './.testRun'
2 | testRun('pnpm run dev')
3 |
--------------------------------------------------------------------------------
/examples/apollo/.test-preview.test.ts:
--------------------------------------------------------------------------------
1 | import { testRun } from './.testRun'
2 | testRun('pnpm run preview')
3 |
--------------------------------------------------------------------------------
/examples/apollo/.testRun.ts:
--------------------------------------------------------------------------------
1 | export { testRun }
2 |
3 | import { test, expect, run, fetchHtml, page, getServerUrl, autoRetry } from '@brillout/test-e2e'
4 |
5 | function testRun(cmd: `pnpm run ${'dev' | 'preview'}`) {
6 | run(cmd)
7 |
8 | const content = 'United States'
9 | const loading = 'Loading contries...'
10 | test('HTML', async () => {
11 | const html = await fetchHtml('/')
12 | expect(getTitle(html)).toBe('My Vike + React App')
13 | // fetchHtml() awaits the stream
14 | expect(html).toContain(content)
15 | })
16 | test('DOM', async () => {
17 | await page.goto(getServerUrl() + '/')
18 | const body = await page.textContent('body')
19 | // Playwright seems to await the HTML stream
20 | expect(body).not.toContain(loading)
21 | expect(body).toContain(content)
22 | await testCounter()
23 | })
24 | }
25 |
26 | function getTitle(html: string) {
27 | const title = html.match(/
(.*?)<\/title>/i)?.[1]
28 | return title
29 | }
30 |
31 | async function testCounter() {
32 | // autoRetry() for awaiting client-side code loading & executing
33 | await autoRetry(
34 | async () => {
35 | expect(await page.textContent('button')).toBe('Counter 0')
36 | await page.click('button')
37 | expect(await page.textContent('button')).toContain('Counter 1')
38 | },
39 | { timeout: 5 * 1000 },
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/examples/apollo/README.md:
--------------------------------------------------------------------------------
1 | Example of using `vike-react-apollo`.
2 |
3 | > [!NOTE]
4 | > For more examples, see [Bati](https://batijs.dev) which generates `vike-react` apps.
5 |
6 | ```bash
7 | git clone git@github.com:vikejs/vike-react
8 | cd vike-react/examples/apollo/
9 | npm install
10 | npm run dev
11 | ```
12 |
--------------------------------------------------------------------------------
/examples/apollo/layouts/HeadDefault.tsx:
--------------------------------------------------------------------------------
1 | export default HeadDefault
2 |
3 | import React from 'react'
4 | import logoUrl from '../assets/logo.svg'
5 |
6 | function HeadDefault() {
7 | return (
8 | <>
9 |
10 |
11 |
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/examples/apollo/layouts/LayoutDefault.tsx:
--------------------------------------------------------------------------------
1 | export default LayoutDefault
2 |
3 | import './style.css'
4 | import React from 'react'
5 | import logoUrl from '../assets/logo.svg'
6 |
7 | function LayoutDefault({ children }: { children: React.ReactNode }) {
8 | return (
9 |
16 |
17 |
18 |
19 | {children}
20 |
21 | )
22 | }
23 |
24 | function Sidebar({ children }: { children: React.ReactNode }) {
25 | return (
26 |
39 | )
40 | }
41 |
42 | function Content({ children }: { children: React.ReactNode }) {
43 | return (
44 |
45 |
53 | {children}
54 |
55 |
56 | )
57 | }
58 |
59 | function Logo() {
60 | return (
61 |
71 | )
72 | }
73 |
--------------------------------------------------------------------------------
/examples/apollo/layouts/style.css:
--------------------------------------------------------------------------------
1 | /* Links */
2 | a {
3 | text-decoration: none;
4 | }
5 | #sidebar a {
6 | padding: 2px 10px;
7 | margin-left: -10px;
8 | }
9 | #sidebar a.is-active {
10 | background-color: #eee;
11 | }
12 |
13 | /* Reset */
14 | body {
15 | margin: 0;
16 | font-family: sans-serif;
17 | }
18 | * {
19 | box-sizing: border-box;
20 | }
21 |
22 | /* Page Transition Anmiation */
23 | #page-content {
24 | opacity: 1;
25 | transition: opacity 0.3s ease-in-out;
26 | }
27 | body.page-is-transitioning #page-content {
28 | opacity: 0;
29 | }
30 |
--------------------------------------------------------------------------------
/examples/apollo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "dev": "vike dev",
4 | "build": "vike build",
5 | "preview": "vike build && vike preview"
6 | },
7 | "dependencies": {
8 | "@types/react": "^19.0.10",
9 | "@types/react-dom": "^19.0.4",
10 | "@vitejs/plugin-react": "^4.3.4",
11 | "react": "^19.0.0",
12 | "react-dom": "^19.0.0",
13 | "typescript": "^5.8.3",
14 | "vike": "^0.4.230",
15 | "vike-react": "0.6.4",
16 | "vike-react-apollo": "0.1.2",
17 | "@apollo/client": "^3.10.8",
18 | "@apollo/client-react-streaming": "^0.11.2",
19 | "graphql": "^16.9.0",
20 | "vite": "^6.2.5"
21 | },
22 | "type": "module"
23 | }
24 |
--------------------------------------------------------------------------------
/examples/apollo/pages/+ApolloClient.ts:
--------------------------------------------------------------------------------
1 | import { ApolloClient, InMemoryCache } from '@apollo/client-react-streaming'
2 | import type { PageContext } from 'vike/types'
3 |
4 | // Same config but with artificial delay: https://gist.github.com/brillout/7d7db0fd6ce55b3b5e8f7ec893eeda01
5 | export default (pageContext: PageContext) =>
6 | new ApolloClient({
7 | uri: 'https://countries.trevorblades.com',
8 | cache: new InMemoryCache(),
9 | })
10 |
--------------------------------------------------------------------------------
/examples/apollo/pages/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'vike/types'
2 | import Layout from '../layouts/LayoutDefault'
3 | import Head from '../layouts/HeadDefault'
4 | import vikeReact from 'vike-react/config'
5 | import vikeReactApollo from 'vike-react-apollo/config'
6 |
7 | // Default configs (can be overridden by pages)
8 | export default {
9 | Layout,
10 | Head,
11 | //
12 | title: 'My Vike + React App',
13 | extends: [vikeReact, vikeReactApollo],
14 | passToClient: ['routeParams'],
15 | } satisfies Config
16 |
--------------------------------------------------------------------------------
/examples/apollo/pages/_error/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page
2 |
3 | import React from 'react'
4 | import { usePageContext } from 'vike-react/usePageContext'
5 |
6 | function Page() {
7 | const { is404 } = usePageContext()
8 | if (is404) {
9 | return (
10 | <>
11 | 404 Page Not Found
12 | This page could not be found.
13 | >
14 | )
15 | } else {
16 | return (
17 | <>
18 | 500 Internal Server Error
19 | Something went wrong.
20 | >
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/apollo/pages/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page
2 |
3 | import React from 'react'
4 | import { Counter } from './Counter'
5 | import { Countries } from './Countries'
6 |
7 | function Page() {
8 | return (
9 | <>
10 | My Vike + React app
11 | This page is:
12 |
13 | Rendered to HTML.
14 |
15 | Interactive while loading.
16 |
17 |
18 |
19 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ullamcorper neque magna, a dapibus turpis
20 | volutpat eget. Praesent et aliquam nisi. Integer congue nec ligula et sollicitudin.
21 |
22 |
23 |
24 | >
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/examples/apollo/pages/index/Counter.tsx:
--------------------------------------------------------------------------------
1 | export { Counter }
2 |
3 | import React, { useState } from 'react'
4 |
5 | function Counter() {
6 | const [count, setCount] = useState(0)
7 |
8 | return setCount((count) => count + 1)}>Counter {count}
9 | }
10 |
--------------------------------------------------------------------------------
/examples/apollo/pages/index/Countries.tsx:
--------------------------------------------------------------------------------
1 | export { Countries }
2 |
3 | import { gql, useSuspenseQuery } from '@apollo/client/index.js'
4 | import React from 'react'
5 | import { withFallback } from 'vike-react-apollo'
6 |
7 | const Countries = withFallback(() => {
8 | const { data } = useSuspenseQuery<{ countries: { code: string; name: string }[] }>(gql`
9 | {
10 | countries {
11 | code
12 | name
13 | }
14 | }
15 | `)
16 |
17 | return (
18 |
19 | {data.countries.map((country) => (
20 | {country.name}
21 | ))}
22 |
23 | )
24 | })
25 |
--------------------------------------------------------------------------------
/examples/apollo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "ES2020",
5 | "moduleResolution": "Node",
6 | "target": "ES2020",
7 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
8 | "types": ["vite/client"],
9 | "jsx": "react",
10 | "skipLibCheck": true,
11 | "esModuleInterop": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/apollo/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import vike from 'vike/plugin'
3 | import { UserConfig } from 'vite'
4 |
5 | export default {
6 | plugins: [react(), vike()],
7 | // Seems like Apollo is heavy? Or is there a way to reduce the size of our Apollo imports?
8 | build: { chunkSizeWarningLimit: 600 },
9 | } satisfies UserConfig
10 |
--------------------------------------------------------------------------------
/examples/full/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/examples/full/.test-dev.test.ts:
--------------------------------------------------------------------------------
1 | import { testRun } from './.testRun'
2 | testRun('pnpm run dev')
3 |
--------------------------------------------------------------------------------
/examples/full/.test-preview.test.ts:
--------------------------------------------------------------------------------
1 | import { testRun } from './.testRun'
2 | testRun('pnpm run preview')
3 |
--------------------------------------------------------------------------------
/examples/full/README.md:
--------------------------------------------------------------------------------
1 | Full-fledged example of using `vike-react`.
2 |
3 | > [!NOTE]
4 | > For more examples, see [Bati](https://batijs.dev) which generates `vike-react` apps.
5 |
6 | ```bash
7 | git clone git@github.com:vikejs/vike-react
8 | cd vike-react/examples/full/
9 | npm install
10 | npm run dev
11 | ```
12 |
--------------------------------------------------------------------------------
/examples/full/components/ClientOnlyComponent.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const location = window.document.location
4 |
5 | export default function ClientOnlyComponent() {
6 | // Will be printed only in the browser:
7 | console.log('Rendering the ClientOnlyComponent')
8 |
9 | return (
10 |
11 |
Only loaded in the browser
12 |
window.location.href: {location.href}
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/examples/full/components/Counter.tsx:
--------------------------------------------------------------------------------
1 | export { Counter }
2 |
3 | import React, { useState } from 'react'
4 |
5 | function Counter() {
6 | const [count, setCount] = useState(0)
7 | return (
8 | setCount((count) => count + 1)}>
9 | Counter {count}
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/examples/full/components/Link.tsx:
--------------------------------------------------------------------------------
1 | export { Link }
2 |
3 | import { usePageContext } from 'vike-react/usePageContext'
4 | import React from 'react'
5 |
6 | function Link({ href, children }: { href: string; children: string }) {
7 | const pageContext = usePageContext()
8 | const { urlPathname } = pageContext
9 | const isActive = href === '/' ? urlPathname === href : urlPathname.startsWith(href)
10 | return (
11 |
12 | {children}
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/examples/full/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "dev": "vike dev",
4 | "build": "vike build",
5 | "preview": "vike build && vike preview"
6 | },
7 | "dependencies": {
8 | "@types/react": "^19.0.10",
9 | "@types/react-dom": "^19.0.4",
10 | "@vitejs/plugin-react": "^4.3.4",
11 | "node-fetch": "^3.3.2",
12 | "react": "^19.0.0",
13 | "react-dom": "^19.0.0",
14 | "react-streaming": "^0.4.2",
15 | "typescript": "^5.8.3",
16 | "vike": "^0.4.230",
17 | "vike-react": "0.6.4",
18 | "vite": "^6.2.5"
19 | },
20 | "type": "module"
21 | }
22 |
--------------------------------------------------------------------------------
/examples/full/pages/+Head.tsx:
--------------------------------------------------------------------------------
1 | export { Head }
2 |
3 | import React from 'react'
4 | import logoUrl from '../assets/logo.svg'
5 |
6 | function Head() {
7 | return (
8 | <>
9 |
10 | >
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/examples/full/pages/+Layout.tsx:
--------------------------------------------------------------------------------
1 | export { Layout }
2 |
3 | import './style.css'
4 | import React from 'react'
5 | import logoUrl from '../assets/logo.svg'
6 | import { Link } from '../components/Link'
7 |
8 | function Layout({ children }: { children: React.ReactNode }) {
9 | return (
10 |
17 |
18 |
19 | Welcome
20 | Data Fetching
21 | HTML Streaming
22 | Without SSR
23 | Nested Layout
24 | Client Only
25 | useConfig()
26 |
27 | {children}
28 |
29 | )
30 | }
31 |
32 | function Sidebar({ children }: { children: React.ReactNode }) {
33 | return (
34 |
47 | )
48 | }
49 |
50 | function Content({ children }: { children: React.ReactNode }) {
51 | return (
52 |
53 |
61 | {children}
62 |
63 |
64 | )
65 | }
66 |
67 | function Logo() {
68 | return (
69 |
79 | )
80 | }
81 |
--------------------------------------------------------------------------------
/examples/full/pages/+config.ts:
--------------------------------------------------------------------------------
1 | export { config }
2 |
3 | import type { Config } from 'vike/types'
4 | import vikeReact from 'vike-react/config'
5 |
6 | // Default configs (can be overridden by pages)
7 | const config = {
8 | //
9 | title: 'My Vike + React App',
10 | // https://vike.dev/stream
11 | stream: true,
12 | // https://vike.dev/ssr - this line can be removed since `true` is the default
13 | ssr: true,
14 | bodyAttributes: { class: 'dark' },
15 | viewport: 999,
16 | // https://vike.dev/extends
17 | extends: vikeReact,
18 | } satisfies Config
19 |
--------------------------------------------------------------------------------
/examples/full/pages/+react.client.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'vike/types'
2 |
3 | export default {
4 | hydrateRootOptions: {
5 | identifierPrefix: 'some-id-client-prefix',
6 | },
7 | } satisfies Config['react']
8 |
--------------------------------------------------------------------------------
/examples/full/pages/+react.server.ts:
--------------------------------------------------------------------------------
1 | import type { Config, PageContextServer } from 'vike/types'
2 |
3 | export default (_pageContext: PageContextServer) =>
4 | ({
5 | renderToStringOptions: {
6 | identifierPrefix: 'some-id-server-prefix',
7 | },
8 | }) satisfies Config['react']
9 |
--------------------------------------------------------------------------------
/examples/full/pages/_error/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page
2 |
3 | import React from 'react'
4 | import { usePageContext } from 'vike-react/usePageContext'
5 |
6 | function Page() {
7 | const { is404 } = usePageContext()
8 | if (is404) {
9 | return (
10 | <>
11 | 404 Page Not Found
12 | This page could not be found.
13 | >
14 | )
15 | } else {
16 | return (
17 | <>
18 | 500 Internal Server Error
19 | Something went wrong.
20 | >
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/full/pages/client-only/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page
2 |
3 | import React from 'react'
4 | import { Counter } from '../../components/Counter'
5 | import { clientOnly } from 'vike-react/clientOnly'
6 |
7 | const ClientOnlyComponent = clientOnly(() => import('../../components/ClientOnlyComponent'))
8 |
9 | function Page() {
10 | return (
11 | <>
12 | My Vike + React app
13 | This page is:
14 |
15 | Rendered to HTML.
16 |
17 | Interactive.
18 |
19 |
20 |
21 | >
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/examples/full/pages/images/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page }
2 |
3 | import React from 'react'
4 | import { Head } from 'vike-react/Head'
5 | import logoOld from '../../assets/logo.svg'
6 | import logoNew from '../../assets/logo-new.svg'
7 | import { Counter } from '../../components/Counter'
8 |
9 | function Page() {
10 | return (
11 | <>
12 |
13 | Page showcasing an <Image>
component that adds/teleports structured data (
14 | <script type="application/ld+json">
) to <head>
, see HTML.
15 |
16 |
17 | New logo:
18 |
19 |
20 |
21 | Old logo:
22 |
23 |
24 |
25 | >
26 | )
27 | }
28 |
29 | function Image({ src, author }: { src: string; author: string }) {
30 | return (
31 | <>
32 |
33 |
34 |
47 |
48 | >
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/examples/full/pages/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page
2 |
3 | import React, { useId } from 'react'
4 | import { Counter } from '../../components/Counter'
5 | import image from '../../assets/logo-new.svg'
6 | import { Config } from 'vike-react/Config'
7 |
8 | function Page() {
9 | // Will be printed on the server and in the browser:
10 | console.log('Rendering the landing page')
11 |
12 | const id = useId()
13 | console.log(id)
14 |
15 | return (
16 | <>
17 |
18 | My Vike + React app
19 | This page is:
20 |
21 | Rendered to HTML.
22 |
23 | Interactive.
24 |
25 |
26 | >
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/examples/full/pages/index/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'vike/types'
2 |
3 | export default {
4 | stream: false,
5 | } satisfies Config
6 |
--------------------------------------------------------------------------------
/examples/full/pages/star-wars/@id/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page
2 |
3 | import React from 'react'
4 | import type { Data } from './+data'
5 | import { useData } from 'vike-react/useData'
6 |
7 | function Page() {
8 | const movie = useData()
9 | return (
10 | <>
11 | {movie.title}
12 | Release Date: {movie.release_date}
13 |
14 | Director: {movie.director}
15 |
16 | Producer: {movie.producer}
17 | >
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/examples/full/pages/star-wars/@id/+data.tsx:
--------------------------------------------------------------------------------
1 | // https://vike.dev/data
2 | export { data }
3 | export type Data = Awaited>
4 |
5 | import { useConfig } from 'vike-react/useConfig'
6 | import type { PageContextServer } from 'vike/types'
7 | import type { MovieDetails } from '../types'
8 | import React from 'react'
9 |
10 | const data = async (pageContext: PageContextServer) => {
11 | const config = useConfig()
12 |
13 | const response = await fetch(`https://brillout.github.io/star-wars/api/films/${pageContext.routeParams.id}.json`)
14 | let movie = (await response.json()) as MovieDetails
15 |
16 | const { title } = movie
17 | config({
18 | title, //
19 | Head: (
20 | <>
21 |
22 | >
23 | ),
24 | })
25 |
26 | // We remove data we don't need because the data is passed to the client; we should
27 | // minimize what is sent over the network.
28 | movie = minimize(movie)
29 |
30 | return movie
31 | }
32 |
33 | function minimize(movie: MovieDetails): MovieDetails {
34 | const { id, title, release_date, director, producer } = movie
35 | movie = { id, title, release_date, director, producer }
36 | return movie
37 | }
38 |
--------------------------------------------------------------------------------
/examples/full/pages/star-wars/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page
2 |
3 | import React from 'react'
4 | import type { Data } from './+data'
5 | import { useData } from 'vike-react/useData'
6 |
7 | function Page() {
8 | const movies = useData()
9 | return (
10 | <>
11 | Star Wars Movies
12 |
13 | {movies.map(({ id, title, release_date }) => (
14 |
15 | {title} ({release_date})
16 |
17 | ))}
18 |
19 |
20 | Source: brillout.github.io/star-wars .
21 |
22 | >
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/examples/full/pages/star-wars/index/+data.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/data
2 | export { data }
3 | export type Data = Awaited>
4 |
5 | import fetch from 'node-fetch'
6 | import { useConfig } from 'vike-react/useConfig'
7 | import type { Movie, MovieDetails } from '../types'
8 | import image from '../../../assets/logo-new.svg'
9 |
10 | const data = async () => {
11 | const config = useConfig()
12 |
13 | const response = await fetch('https://brillout.github.io/star-wars/api/films.json')
14 | const moviesData = (await response.json()) as MovieDetails[]
15 |
16 | const n = moviesData.length
17 | config({
18 | title: `${n} Star Wars Movies`, //
19 | description: `All the ${n} movies from the Star Wars franchise`, //
20 | image, //
21 | })
22 |
23 | // We remove data we don't need because the data is passed to the client; we should
24 | // minimize what is sent over the network.
25 | const movies = minimize(moviesData)
26 |
27 | return movies
28 | }
29 |
30 | function minimize(movies: MovieDetails[]): Movie[] {
31 | return movies.map((movie) => {
32 | const { title, release_date, id } = movie
33 | return { title, release_date, id }
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/examples/full/pages/star-wars/types.ts:
--------------------------------------------------------------------------------
1 | export type Movie = {
2 | id: string
3 | title: string
4 | release_date: string
5 | }
6 | export type MovieDetails = Movie & {
7 | director: string
8 | producer: string
9 | }
10 |
--------------------------------------------------------------------------------
/examples/full/pages/starship/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page }
2 |
3 | import React from 'react'
4 |
5 | function Page() {
6 | return (
7 | <>
8 | Overview
9 | The Starship will, at term, repalce all SpaceX's rocket models.
10 | The mission: Make life multi planetary.
11 | Starship drastically reduces the cost of sending payload to space, ensuring SpaceX's financial prosperity.
12 | >
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/examples/full/pages/starship/+config.ts:
--------------------------------------------------------------------------------
1 | export { config }
2 |
3 | import type { Config } from 'vike/types'
4 |
5 | const config = {
6 | // https://vike.dev/keepScrollPosition
7 | keepScrollPosition: true,
8 | } satisfies Config
9 |
--------------------------------------------------------------------------------
/examples/full/pages/starship/reviews/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page }
2 |
3 | import React from 'react'
4 |
5 | function Page() {
6 | return (
7 | <>
8 | Reviews
9 | "The Starship brought me and my family to Mars safely." -- Anonymous Family
10 | "A handful of Starships was enough to set up SkyNet. It worked like a charm." -- Skynet Research
11 | >
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/examples/full/pages/starship/spec/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page }
2 |
3 | import React from 'react'
4 |
5 | function Page() {
6 | return (
7 | <>
8 | Spec
9 |
10 | {[
11 | 'HEIGHT 50 m / 164 ft',
12 | 'DIAMETER 9 m / 30 ft',
13 | 'PROPELLANT CAPACITY 1200 t / 2.6 Mlb',
14 | 'THRUST 1500 tf / 3.2Mlbf',
15 | 'PAYLOAD CAPACITY 100-150 t orbit dependent',
16 | ].join('\n')}
17 |
18 | >
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/examples/full/pages/streaming/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page
2 |
3 | import React, { Suspense } from 'react'
4 | import { useAsync } from 'react-streaming'
5 | import { Counter } from '../../components/Counter'
6 |
7 | function Page() {
8 | return (
9 | <>
10 | Star Wars Movies
11 |
12 | Same as /star-wars
page, but showcasing HTML Streaming and{' '}
13 | Progressive Rendering . (Note how the interactive
14 | counter works before the data finished loading.)
15 |
16 |
17 | Loading...}>
18 |
19 |
20 | >
21 | )
22 | }
23 |
24 | function MovieList() {
25 | const movies = useAsync(['star-wars-movies'], async () => {
26 | const response = await fetch('https://star-wars.brillout.com/api/films.json')
27 | // Simulate slow network
28 | await new Promise((r) => setTimeout(r, 3 * 1000))
29 | const movies: Movie[] = (await response.json()).results
30 | return movies
31 | })
32 |
33 | return (
34 |
35 | {movies.map((movies, index) => (
36 |
37 | {movies.title} ({movies.release_date})
38 |
39 | ))}
40 |
41 | )
42 | }
43 |
44 | export type Movie = {
45 | title: string
46 | release_date: string
47 | }
48 |
--------------------------------------------------------------------------------
/examples/full/pages/style.css:
--------------------------------------------------------------------------------
1 | /* Links */
2 | a {
3 | text-decoration: none;
4 | }
5 | #sidebar a {
6 | padding: 2px 10px;
7 | margin-left: -10px;
8 | }
9 | #sidebar a.is-active {
10 | background-color: #eee;
11 | }
12 |
13 | /* Reset */
14 | body {
15 | margin: 0;
16 | font-family: sans-serif;
17 | }
18 | * {
19 | box-sizing: border-box;
20 | }
21 |
22 | /* Page Transition Anmiation */
23 | #page-content {
24 | opacity: 1;
25 | transition: opacity 0.3s ease-in-out;
26 | }
27 | body.page-is-transitioning #page-content {
28 | opacity: 0;
29 | }
30 |
31 | /* Inline code blocks */
32 | code {
33 | font-family: monospace;
34 | background-color: #eaeaea;
35 | padding: 3px 5px;
36 | border-radius: 4px;
37 | }
38 |
--------------------------------------------------------------------------------
/examples/full/pages/without-ssr/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page
2 |
3 | import React from 'react'
4 | import { Counter } from '../../components/Counter'
5 |
6 | function Page() {
7 | // Will be printed only in the browser:
8 | console.log('Rendering page without SSR')
9 |
10 | return (
11 | <>
12 | Without SSR
13 | This page is rendered only in the browser:
14 |
15 |
16 | It's interactive.
17 |
18 | It isn't rendered to HTML.
19 |
20 | >
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/examples/full/pages/without-ssr/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'vike/types'
2 |
3 | export default {
4 | // https://vike.dev/ssr
5 | ssr: false,
6 | title: 'No SSR',
7 | } satisfies Config
8 |
--------------------------------------------------------------------------------
/examples/full/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "ES2020",
5 | "moduleResolution": "Node",
6 | "target": "ES2020",
7 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
8 | "types": ["vite/client"],
9 | "jsx": "react",
10 | "skipLibCheck": true,
11 | "esModuleInterop": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/full/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import vike from 'vike/plugin'
3 | import { UserConfig } from 'vite'
4 |
5 | export default {
6 | plugins: [react(), vike()],
7 | } satisfies UserConfig
8 |
--------------------------------------------------------------------------------
/examples/minimal/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/examples/minimal/.test-dev.test.ts:
--------------------------------------------------------------------------------
1 | import { testRun } from './.testRun'
2 | testRun('pnpm run dev')
3 |
--------------------------------------------------------------------------------
/examples/minimal/.test-preview.test.ts:
--------------------------------------------------------------------------------
1 | import { testRun } from './.testRun'
2 | testRun('pnpm run preview')
3 |
--------------------------------------------------------------------------------
/examples/minimal/.testRun.ts:
--------------------------------------------------------------------------------
1 | export { testRunClassic as testRun }
2 |
3 | import { test, expect, run, fetchHtml, page, getServerUrl, autoRetry } from '@brillout/test-e2e'
4 |
5 | function testRunClassic(cmd: 'pnpm run dev' | 'pnpm run preview') {
6 | run(cmd)
7 |
8 | test('page content is rendered to HTML', async () => {
9 | const html = await fetchHtml('/')
10 | expect(html).toContain('Welcome ')
11 | })
12 |
13 | test('page is rendered to the DOM and interactive', async () => {
14 | await page.goto(getServerUrl() + '/')
15 | await page.click('a[href="/"]')
16 | expect(await page.textContent('h1')).toBe('Welcome')
17 | await testCounter()
18 | })
19 |
20 | test('about page', async () => {
21 | await page.click('a[href="/about"]')
22 | await autoRetry(async () => {
23 | expect(await page.textContent('h1')).toBe('About')
24 | })
25 | expect(await page.textContent('p')).toBe('Example of using Vike.')
26 | const html = await fetchHtml('/about')
27 | expect(html).toContain('About ')
28 | })
29 | }
30 |
31 | async function testCounter(currentValue = 0) {
32 | // autoRetry() in case page just got client-side navigated
33 | await autoRetry(
34 | async () => {
35 | const btn = page.locator('button', { hasText: 'Counter' })
36 | expect(await btn.textContent()).toBe(`Counter ${currentValue}`)
37 | },
38 | { timeout: 5 * 1000 },
39 | )
40 | // autoRetry() in case page isn't hydrated yet
41 | await autoRetry(
42 | async () => {
43 | const btn = page.locator('button', { hasText: 'Counter' })
44 | await btn.click()
45 | expect(await btn.textContent()).toBe(`Counter ${currentValue + 1}`)
46 | },
47 | { timeout: 5 * 1000 },
48 | )
49 | }
50 |
--------------------------------------------------------------------------------
/examples/minimal/README.md:
--------------------------------------------------------------------------------
1 | Minimal example of using `vike-react`.
2 |
3 | > [!NOTE]
4 | > For more examples, see [Bati](https://batijs.dev) which generates `vike-react` apps.
5 |
6 | ```bash
7 | git clone git@github.com:vikejs/vike-react
8 | cd vike-react/examples/minimal/
9 | npm install
10 | npm run dev
11 | ```
12 |
--------------------------------------------------------------------------------
/examples/minimal/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "dev": "vike dev",
4 | "build": "vike build",
5 | "preview": "vike build && vike preview"
6 | },
7 | "dependencies": {
8 | "@vitejs/plugin-react": "4.2.1",
9 | "react": "^19.0.0",
10 | "react-dom": "^19.0.0",
11 | "vike": "^0.4.230",
12 | "vike-react": "0.6.4",
13 | "vite": "^6.2.5"
14 | },
15 | "type": "module"
16 | }
17 |
--------------------------------------------------------------------------------
/examples/minimal/pages/+config.js:
--------------------------------------------------------------------------------
1 | export { config }
2 |
3 | import vikeReact from 'vike-react/config'
4 | import { Layout } from './Layout'
5 |
6 | const config = {
7 | // https://vike.dev/Layout
8 | Layout: Layout,
9 | // https://vike.dev/extends
10 | extends: vikeReact,
11 | }
12 |
--------------------------------------------------------------------------------
/examples/minimal/pages/Layout.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: sans-serif;
4 | }
5 | * {
6 | box-sizing: border-box;
7 | }
8 | a {
9 | text-decoration: none;
10 | }
11 |
12 | .navitem {
13 | padding: 3px;
14 | }
15 |
--------------------------------------------------------------------------------
/examples/minimal/pages/Layout.jsx:
--------------------------------------------------------------------------------
1 | export { Layout }
2 |
3 | import React from 'react'
4 | import './Layout.css'
5 |
6 | function Layout({ children }) {
7 | return (
8 |
9 |
10 |
11 | Home
12 |
13 |
14 | About
15 |
16 |
17 | {children}
18 |
19 | )
20 | }
21 |
22 | function PageLayout({ children }) {
23 | return (
24 |
31 | {children}
32 |
33 | )
34 | }
35 |
36 | function Sidebar({ children }) {
37 | return (
38 |
49 | {children}
50 |
51 | )
52 | }
53 |
54 | function Content({ children }) {
55 | return (
56 |
64 | {children}
65 |
66 | )
67 | }
68 |
--------------------------------------------------------------------------------
/examples/minimal/pages/about/+Page.jsx:
--------------------------------------------------------------------------------
1 | export default Page
2 |
3 | import React from 'react'
4 |
5 | function Page() {
6 | return (
7 | <>
8 | About
9 | Example of using Vike.
10 | >
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/examples/minimal/pages/index/+Page.jsx:
--------------------------------------------------------------------------------
1 | export default Page
2 |
3 | import React from 'react'
4 | import { Counter } from './Counter'
5 |
6 | function Page() {
7 | return (
8 | <>
9 | Welcome
10 | This page is:
11 |
12 | Rendered to HTML.
13 |
14 | Interactive.
15 |
16 |
17 | >
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/examples/minimal/pages/index/Counter.jsx:
--------------------------------------------------------------------------------
1 | export { Counter }
2 |
3 | import React, { useState } from 'react'
4 |
5 | function Counter() {
6 | const [count, setCount] = useState(0)
7 | return (
8 | setCount((count) => count + 1)}>
9 | Counter {count}
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/examples/minimal/vite.config.js:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import vike from 'vike/plugin'
3 |
4 | export default {
5 | plugins: [react(), vike()],
6 | }
7 |
--------------------------------------------------------------------------------
/examples/query/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/examples/query/.test-dev.test.ts:
--------------------------------------------------------------------------------
1 | import { testRun } from './.testRun'
2 | testRun('pnpm run dev')
3 |
--------------------------------------------------------------------------------
/examples/query/.test-preview.test.ts:
--------------------------------------------------------------------------------
1 | import { testRun } from './.testRun'
2 | testRun('pnpm run preview')
3 |
--------------------------------------------------------------------------------
/examples/query/.testRun.ts:
--------------------------------------------------------------------------------
1 | export { testRun }
2 |
3 | import { test, expect, run, page, getServerUrl, autoRetry } from '@brillout/test-e2e'
4 |
5 | function testRun(cmd: `pnpm run ${'dev' | 'preview'}`) {
6 | run(cmd)
7 |
8 | const content = 'Return of the Jedi'
9 | const loading = 'Loading movies...'
10 | const titleDefault = 'My Vike + React App'
11 | const titleOverriden = '6 movies'
12 | const titleAsScript = ``
13 | const description = ' '
14 | test('HTML (as user)', async () => {
15 | const html = await fetchAsUser('/')
16 | expect(html).toContain(content)
17 | expect(html).toContain(loading)
18 | expect(html).toContain(titleAsScript)
19 | expect(getTitle(html)).toBe(titleDefault)
20 | expect(html.split('').length).toBe(2)
21 | expect(html).not.toContain(description)
22 | })
23 | test('HTML (as bot)', async () => {
24 | const html = await fetchAsBot('/')
25 | expect(html).toContain(content)
26 | expect(html).not.toContain(loading)
27 | expect(html).not.toContain(titleAsScript)
28 | expect(getTitle(html)).toBe(titleOverriden)
29 | expect(html.split('').length).toBe(2)
30 | expect(html).toContain(description)
31 | })
32 | test('DOM', async () => {
33 | await page.goto(getServerUrl() + '/')
34 | const body = await page.textContent('body')
35 | // Playwright seems to await the HTML stream
36 | expect(body).not.toContain(loading)
37 | expect(body).toContain(content)
38 | await testCounter()
39 | })
40 | }
41 |
42 | function getTitle(html: string) {
43 | const title = html.match(/(.*?)<\/title>/i)?.[1]
44 | return title
45 | }
46 |
47 | async function testCounter() {
48 | // autoRetry() for awaiting client-side code loading & executing
49 | await autoRetry(
50 | async () => {
51 | expect(await page.textContent('button')).toBe('Counter 0')
52 | await page.click('button')
53 | expect(await page.textContent('button')).toContain('Counter 1')
54 | },
55 | { timeout: 5 * 1000 },
56 | )
57 | }
58 |
59 | async function fetchAsBot(pathname: string) {
60 | return await fetchHtml(pathname, 'curl/8.5.0')
61 | }
62 | async function fetchAsUser(pathname: string) {
63 | return await fetchHtml(pathname, 'chrome')
64 | }
65 | async function fetchHtml(pathname: string, userAgent: string) {
66 | const response = await fetch(getServerUrl() + pathname, { headers: { ['User-Agent']: userAgent } })
67 | const html = await response.text()
68 | return html
69 | }
70 |
--------------------------------------------------------------------------------
/examples/query/README.md:
--------------------------------------------------------------------------------
1 | Example of using `vike-react-query`.
2 |
3 | > [!NOTE]
4 | > For more examples, see [Bati](https://batijs.dev) which generates `vike-react` apps.
5 |
6 | ```bash
7 | git clone git@github.com:vikejs/vike-react
8 | cd vike-react/examples/query/
9 | npm install
10 | npm run dev
11 | ```
12 |
--------------------------------------------------------------------------------
/examples/query/layouts/HeadDefault.tsx:
--------------------------------------------------------------------------------
1 | export default HeadDefault
2 |
3 | import React from 'react'
4 | import logoUrl from '../assets/logo.svg'
5 |
6 | function HeadDefault() {
7 | return (
8 | <>
9 |
10 |
11 | >
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/examples/query/layouts/LayoutDefault.tsx:
--------------------------------------------------------------------------------
1 | export default LayoutDefault
2 |
3 | import './style.css'
4 | import React from 'react'
5 | import logoUrl from '../assets/logo.svg'
6 |
7 | function LayoutDefault({ children }: { children: React.ReactNode }) {
8 | return (
9 |
16 |
17 |
18 |
19 | {children}
20 |
21 | )
22 | }
23 |
24 | function Sidebar({ children }: { children: React.ReactNode }) {
25 | return (
26 |
39 | )
40 | }
41 |
42 | function Content({ children }: { children: React.ReactNode }) {
43 | return (
44 |
45 |
53 | {children}
54 |
55 |
56 | )
57 | }
58 |
59 | function Logo() {
60 | return (
61 |
71 | )
72 | }
73 |
--------------------------------------------------------------------------------
/examples/query/layouts/style.css:
--------------------------------------------------------------------------------
1 | /* Links */
2 | a {
3 | text-decoration: none;
4 | }
5 | #sidebar a {
6 | padding: 2px 10px;
7 | margin-left: -10px;
8 | }
9 | #sidebar a.is-active {
10 | background-color: #eee;
11 | }
12 |
13 | /* Reset */
14 | body {
15 | margin: 0;
16 | font-family: sans-serif;
17 | }
18 | * {
19 | box-sizing: border-box;
20 | }
21 |
22 | /* Page Transition Anmiation */
23 | #page-content {
24 | opacity: 1;
25 | transition: opacity 0.3s ease-in-out;
26 | }
27 | body.page-is-transitioning #page-content {
28 | opacity: 0;
29 | }
30 |
--------------------------------------------------------------------------------
/examples/query/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "dev": "vike dev",
4 | "preview": "vike build && vike preview"
5 | },
6 | "dependencies": {
7 | "@types/react": "^19.0.10",
8 | "@types/react-dom": "^19.0.4",
9 | "@vitejs/plugin-react": "^4.3.4",
10 | "react": "^19.0.0",
11 | "react-dom": "^19.0.0",
12 | "typescript": "^5.8.3",
13 | "vike": "^0.4.230",
14 | "vike-react": "0.6.4",
15 | "vike-react-query": "0.1.4",
16 | "@tanstack/react-query": "^5.20.1",
17 | "vite": "^6.2.5"
18 | },
19 | "type": "module"
20 | }
21 |
--------------------------------------------------------------------------------
/examples/query/pages/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'vike/types'
2 | import Layout from '../layouts/LayoutDefault'
3 | import Head from '../layouts/HeadDefault'
4 | import vikeReact from 'vike-react/config'
5 | import vikeReactQuery from 'vike-react-query/config'
6 |
7 | // Default configs (can be overridden by pages)
8 | export default {
9 | Layout,
10 | Head,
11 | //
12 | title: 'My Vike + React App',
13 | extends: [vikeReact, vikeReactQuery],
14 | passToClient: ['routeParams'],
15 | } satisfies Config
16 |
--------------------------------------------------------------------------------
/examples/query/pages/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page }
2 |
3 | import React from 'react'
4 | import { Counter } from './Counter'
5 | import { Movies } from './Movies'
6 |
7 | function Page() {
8 | return (
9 | <>
10 | My Vike + React app
11 | This page is:
12 |
13 | Rendered to HTML.
14 |
15 | Interactive while loading.
16 |
17 |
18 |
19 | >
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/examples/query/pages/index/@id/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page }
2 |
3 | import React from 'react'
4 | import { usePageContext } from 'vike-react/usePageContext'
5 | import { Movie } from './Movie'
6 |
7 | function Page() {
8 | const pageContext = usePageContext()
9 | const id = pageContext.routeParams!['id']
10 | return
11 | }
12 |
--------------------------------------------------------------------------------
/examples/query/pages/index/@id/Movie.tsx:
--------------------------------------------------------------------------------
1 | export { Movie }
2 |
3 | import React from 'react'
4 | import { withFallback } from 'vike-react-query'
5 | import { useConfig } from 'vike-react/useConfig'
6 | import { useSuspenseQuery } from '@tanstack/react-query'
7 | import { MovieDetails } from '../types'
8 |
9 | const Movie = withFallback(
10 | ({ id }: { id: string }) => {
11 | const config = useConfig()
12 | const result = useSuspenseQuery({
13 | queryKey: ['movie', id],
14 | queryFn: () => getStarWarsMovie(id),
15 | // Disabled to showcase error fallback
16 | retry: false,
17 | })
18 |
19 | const { title, release_date } = result.data
20 | config({
21 | title, //
22 | Head: (
23 | <>
24 |
25 | >
26 | ),
27 | })
28 |
29 | return (
30 | <>
31 | Star Wars Movies
32 |
33 |
34 | Title: {title}
35 |
36 |
37 | Release date: {release_date}
38 |
39 |
40 |
41 | Source: star-wars.brillout.com .
42 |
43 | >
44 | )
45 | },
46 | {
47 | Loading: ({ id }) => `Loading movie ${id}`,
48 | // Try commenting out the error fallback
49 | Error: ({ id, error, retry }) => (
50 | <>
51 | Loading movie {id} failed
52 | {error.message}
53 | retry()}>Try again
54 | >
55 | ),
56 | },
57 | )
58 |
59 | async function getStarWarsMovie(id: string): Promise {
60 | await new Promise((r) => setTimeout(r, 500))
61 |
62 | if (Math.random() > 0.4) {
63 | throw new Error('Failed to fetch')
64 | }
65 |
66 | const response = await fetch(`https://star-wars.brillout.com/api/films/${id}.json`)
67 | return response.json()
68 | }
69 |
--------------------------------------------------------------------------------
/examples/query/pages/index/Counter.tsx:
--------------------------------------------------------------------------------
1 | export { Counter }
2 |
3 | import React, { useState } from 'react'
4 |
5 | function Counter() {
6 | const [count, setCount] = useState(0)
7 |
8 | return setCount((count) => count + 1)}>Counter {count}
9 | }
10 |
--------------------------------------------------------------------------------
/examples/query/pages/index/Movies.tsx:
--------------------------------------------------------------------------------
1 | export { Movies }
2 |
3 | import React from 'react'
4 | import { withFallback } from 'vike-react-query'
5 | import { useSuspenseQuery } from '@tanstack/react-query'
6 | import { navigate } from 'vike/client/router'
7 | import { MovieDetails } from './types'
8 | import { Config } from 'vike-react/Config'
9 | import { Head } from 'vike-react/Head'
10 |
11 | const Movies = withFallback(() => {
12 | const result = useSuspenseQuery({
13 | queryKey: ['movies'],
14 | queryFn: getStarWarsMovies,
15 | })
16 |
17 | const movies = result.data
18 | const onNavigate = (id: string) => {
19 | navigate(`/${id}`)
20 | }
21 |
22 | return (
23 | <>
24 |
25 |
26 |
27 |
28 | Star Wars Movies
29 |
30 | {movies.map(({ id, title, release_date }) => (
31 |
32 | onNavigate(id)}>{title} ({release_date})
33 |
34 | ))}
35 |
36 |
37 | Source: star-wars.brillout.com .
38 |
39 | >
40 | )
41 | }, 'Loading movies...')
42 |
43 | async function getStarWarsMovies(): Promise {
44 | // Simulate slow network
45 | await new Promise((r) => setTimeout(r, 2000))
46 |
47 | const response = await fetch('https://star-wars.brillout.com/api/films.json')
48 | let movies: MovieDetails[] = ((await response.json()) as any).results
49 | movies = movies.map((movie: MovieDetails, i: number) => ({
50 | ...movie,
51 | id: String(i + 1),
52 | }))
53 | return movies
54 | }
55 |
--------------------------------------------------------------------------------
/examples/query/pages/index/types.ts:
--------------------------------------------------------------------------------
1 | export type MovieDetails = {
2 | id: string
3 | title: string
4 | release_date: string
5 | director: string
6 | producer: string
7 | }
8 |
--------------------------------------------------------------------------------
/examples/query/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "ES2020",
5 | "moduleResolution": "Node",
6 | "target": "ES2020",
7 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
8 | "types": ["vite/client"],
9 | "jsx": "react",
10 | "skipLibCheck": true,
11 | "esModuleInterop": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/query/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import vike from 'vike/plugin'
3 | import { UserConfig } from 'vite'
4 |
5 | export default {
6 | plugins: [react(), vike()],
7 | } satisfies UserConfig
8 |
--------------------------------------------------------------------------------
/examples/redux/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/examples/redux/.test-dev.test.ts:
--------------------------------------------------------------------------------
1 | import { testRun } from './.testRun'
2 | testRun('pnpm run dev')
3 |
--------------------------------------------------------------------------------
/examples/redux/.test-preview-ssg.test.ts:
--------------------------------------------------------------------------------
1 | import { testRun } from './.testRun'
2 | testRun('pnpm run preview:ssg')
3 |
--------------------------------------------------------------------------------
/examples/redux/.test-preview.test.ts:
--------------------------------------------------------------------------------
1 | import { testRun } from './.testRun'
2 | testRun('pnpm run preview')
3 |
--------------------------------------------------------------------------------
/examples/redux/.testRun.ts:
--------------------------------------------------------------------------------
1 | export { testRun }
2 |
3 | import { test, expect, run, page, getServerUrl, autoRetry, fetchHtml } from '@brillout/test-e2e'
4 |
5 | function testRun(cmd: `pnpm run ${'dev' | 'preview' | 'preview:ssg'}`) {
6 | run(cmd)
7 |
8 | test('count', async () => {
9 | await page.goto(getServerUrl() + '/')
10 | await testCounter()
11 | await clientSideNavigation()
12 | await fullPageReload()
13 | })
14 | async function clientSideNavigation() {
15 | await page.click('a:has-text("About")')
16 | await page.waitForFunction(() => (window as any)._vike.fullyRenderedUrl === '/about')
17 | await testCounter(1)
18 | await page.click('a:has-text("Welcome")')
19 | await page.waitForFunction(() => (window as any)._vike.fullyRenderedUrl === '/')
20 | await testCounter(2)
21 | }
22 | async function fullPageReload() {
23 | await page.goto(getServerUrl() + '/about')
24 | await testCounter()
25 | await page.goto(getServerUrl() + '/')
26 | await testCounter()
27 | }
28 |
29 | test('todos - initial list', async () => {
30 | await page.goto(getServerUrl() + '/')
31 | await expectInitialList()
32 | })
33 | async function expectInitialList() {
34 | const buyApples = 'Buy apples'
35 | const nodeVerison = `Node.js ${process.version}`
36 | {
37 | const html = await fetchHtml('/')
38 | expect(html).toContain(`${buyApples} `)
39 | expect(html).toContain(nodeVerison)
40 | }
41 | {
42 | const bodyText = await page.textContent('body')
43 | expect(bodyText).toContain(buyApples)
44 | expect(bodyText).toContain(nodeVerison)
45 | expect(await getNumberOfItems()).toBe(2)
46 | }
47 | }
48 |
49 | test('todos - add to-do', async () => {
50 | await page.fill('input[type="text"]', 'Buy bananas')
51 | await page.click('button[type="submit"]')
52 | const expectBananas = async () => {
53 | await autoRetry(async () => {
54 | expect(await getNumberOfItems()).toBe(3)
55 | })
56 | expect(await page.textContent('body')).toContain('Buy bananas')
57 | }
58 | await expectBananas()
59 |
60 | await testCounter()
61 | await clientSideNavigation()
62 | await expectBananas()
63 |
64 | // Full page reload
65 | await fullPageReload()
66 | await expectInitialList()
67 | })
68 | }
69 |
70 | async function getNumberOfItems() {
71 | return await page.evaluate(() => document.querySelectorAll('#todo-list li').length)
72 | }
73 |
74 | async function testCounter(inc: 0 | 1 | 2 = 0) {
75 | const counterInitValue = 42
76 | const currentValue = counterInitValue + inc
77 | // autoRetry() in case page just got client-side navigated
78 | await autoRetry(
79 | async () => {
80 | const btn = page.locator('button', { hasText: 'Counter' })
81 | expect(await btn.textContent()).toBe(`Counter ${currentValue}`)
82 | },
83 | { timeout: 5 * 1000 },
84 | )
85 | // autoRetry() in case page isn't hydrated yet
86 | await autoRetry(
87 | async () => {
88 | const btn = page.locator('button', { hasText: 'Counter' })
89 | await btn.click()
90 | expect(await btn.textContent()).toBe(`Counter ${currentValue + 1}`)
91 | },
92 | { timeout: 5 * 1000 },
93 | )
94 | }
95 |
--------------------------------------------------------------------------------
/examples/redux/README.md:
--------------------------------------------------------------------------------
1 | Example of using `vike-react-redux`.
2 |
3 | ```bash
4 | git clone git@github.com:vikejs/vike-react
5 | cd vike-react/examples/redux/
6 | npm install
7 | npm run dev
8 | ```
9 |
--------------------------------------------------------------------------------
/examples/redux/components/Counter.tsx:
--------------------------------------------------------------------------------
1 | export { Counter }
2 |
3 | import React from 'react'
4 | import { useAppDispatch, useAppSelector } from '../store/hooks'
5 | import { increment, selectCount } from '../store/slices/count'
6 |
7 | function Counter() {
8 | const dispatch = useAppDispatch()
9 | const count = useAppSelector(selectCount)
10 | return (
11 | dispatch(increment())}>
12 | Counter {count}
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/examples/redux/components/Counter/fetchCountInit.ts:
--------------------------------------------------------------------------------
1 | export { fetchCountInit }
2 |
3 | // Pretending the value is fetched over the network
4 | async function fetchCountInit() {
5 | return 42
6 | }
7 |
--------------------------------------------------------------------------------
/examples/redux/components/Link.tsx:
--------------------------------------------------------------------------------
1 | export { Link }
2 |
3 | import { usePageContext } from 'vike-react/usePageContext'
4 | import React from 'react'
5 |
6 | function Link({ href, children }: { href: string; children: string }) {
7 | const pageContext = usePageContext()
8 | const { urlPathname } = pageContext
9 | const isActive = href === '/' ? urlPathname === href : urlPathname.startsWith(href)
10 | return (
11 |
12 | {children}
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/examples/redux/layouts/LayoutDefault.tsx:
--------------------------------------------------------------------------------
1 | export default LayoutDefault
2 |
3 | import './style.css'
4 | import React from 'react'
5 | import logoUrl from './logo.svg'
6 | import { Link } from '../components/Link'
7 |
8 | function LayoutDefault({ children }: { children: React.ReactNode }) {
9 | return (
10 |
17 |
18 |
19 | Welcome
20 | About
21 |
22 | {children}
23 |
24 | )
25 | }
26 |
27 | function Sidebar({ children }: { children: React.ReactNode }) {
28 | return (
29 |
42 | )
43 | }
44 |
45 | function Content({ children }: { children: React.ReactNode }) {
46 | return (
47 |
48 |
56 | {children}
57 |
58 |
59 | )
60 | }
61 |
62 | function Logo() {
63 | return (
64 |
74 | )
75 | }
76 |
--------------------------------------------------------------------------------
/examples/redux/layouts/style.css:
--------------------------------------------------------------------------------
1 | /* Links */
2 | a {
3 | text-decoration: none;
4 | }
5 | #sidebar a {
6 | padding: 2px 10px;
7 | margin-left: -10px;
8 | }
9 | #sidebar a.is-active {
10 | background-color: #eee;
11 | }
12 |
13 | /* Reset */
14 | body {
15 | margin: 0;
16 | font-family: sans-serif;
17 | }
18 | * {
19 | box-sizing: border-box;
20 | }
21 |
22 | /* Page Transition Anmiation */
23 | #page-content {
24 | opacity: 1;
25 | transition: opacity 0.3s ease-in-out;
26 | }
27 | body.page-is-transitioning #page-content {
28 | opacity: 0;
29 | }
30 |
--------------------------------------------------------------------------------
/examples/redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "dev": "vike dev",
4 | "build": "vike build",
5 | "preview": "vike build && vike preview",
6 | "preview:ssg": "vike build --prerender && vike preview --prerender"
7 | },
8 | "dependencies": {
9 | "@reduxjs/toolkit": "^2.6.1",
10 | "@types/react": "^19.0.10",
11 | "@types/react-dom": "^19.0.4",
12 | "@vitejs/plugin-react": "^4.3.4",
13 | "react": "^19.0.0",
14 | "react-dom": "^19.0.0",
15 | "react-redux": "^9.2.0",
16 | "typescript": "^5.8.3",
17 | "vike": "^0.4.230",
18 | "vike-react": "0.6.4",
19 | "vike-react-redux": "0.1.0",
20 | "vite": "^6.2.5"
21 | },
22 | "type": "module"
23 | }
24 |
--------------------------------------------------------------------------------
/examples/redux/pages/+config.ts:
--------------------------------------------------------------------------------
1 | import Layout from '../layouts/LayoutDefault'
2 | import vikeReact from 'vike-react/config'
3 | import vikeReactRedux from 'vike-react-redux/config'
4 | import type { Config } from 'vike/types'
5 |
6 | export default {
7 | Layout,
8 | extends: [vikeReact, vikeReactRedux],
9 | } satisfies Config
10 |
--------------------------------------------------------------------------------
/examples/redux/pages/+redux.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from '../store/createStore'
2 | export default { createStore }
3 |
--------------------------------------------------------------------------------
/examples/redux/pages/about/+Page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Counter } from '../../components/Counter'
3 |
4 | export default function Page() {
5 | return (
6 | <>
7 | About
8 | The counter value is the same as on the Welcome page.
9 |
10 | >
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/examples/redux/pages/about/+config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'vike/types'
2 |
3 | export default {
4 | ssr: false,
5 | } satisfies Config
6 |
--------------------------------------------------------------------------------
/examples/redux/pages/about/+data.ts:
--------------------------------------------------------------------------------
1 | // Environment: server
2 | export { data }
3 | export type Data = Awaited>
4 |
5 | import { fetchCountInit } from '../../components/Counter/fetchCountInit'
6 | import type { PageContextServer } from 'vike/types'
7 |
8 | async function data(pageContext: PageContextServer) {
9 | const countInitial = await fetchCountInit()
10 | return {
11 | countInitial,
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/redux/pages/about/+onData.ts:
--------------------------------------------------------------------------------
1 | // Environment: server, client
2 | export { onData }
3 |
4 | import type { PageContext } from 'vike/types'
5 | import type { Data } from './+data'
6 | import { initializeCount } from '../../store/slices/count'
7 |
8 | function onData(pageContext: PageContext & { data?: Data }) {
9 | const { store } = pageContext
10 | store.dispatch(initializeCount(pageContext.data!.countInitial))
11 |
12 | // Saving KBs: we don't need pageContext.data (we use the store instead)
13 | // - If we don't delete pageContext.data then Vike sends pageContext.data to the client-side
14 | // - This optimization only works if the page is SSR'd: if the page is pre-rendered then don't do this
15 | if (!pageContext.isPrerendering) delete pageContext.data
16 | }
17 |
--------------------------------------------------------------------------------
/examples/redux/pages/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Counter } from '../../components/Counter'
3 | import { TodoList } from './TodoList'
4 |
5 | export default function Page() {
6 | return (
7 | <>
8 | My Vike app
9 | This page is:
10 |
11 | Rendered to HTML.
12 |
13 | Interactive.
14 |
15 |
16 |
17 | >
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/examples/redux/pages/index/+data.ts:
--------------------------------------------------------------------------------
1 | // Environment: server
2 | export { data }
3 | export type Data = Awaited>
4 |
5 | import { fetchCountInit } from '../../components/Counter/fetchCountInit'
6 | import type { PageContextServer } from 'vike/types'
7 |
8 | async function data(pageContext: PageContextServer) {
9 | const [countInitial, todoItemsInitial] = await Promise.all([fetchCountInit(), fetchTodosInit()])
10 | return { countInitial, todoItemsInitial }
11 | }
12 |
13 | // Pretending the list is fetched over the network
14 | async function fetchTodosInit() {
15 | return [
16 | //
17 | { text: 'Buy apples' },
18 | { text: `Update Node.js ${process.version} to latest version` },
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/examples/redux/pages/index/+onData.ts:
--------------------------------------------------------------------------------
1 | // Environment: server, client
2 | export { onData }
3 |
4 | import type { PageContext } from 'vike/types'
5 | import type { Data } from './+data'
6 | import { initializeCount } from '../../store/slices/count'
7 | import { initializeTodos } from '../../store/slices/todos'
8 |
9 | function onData(pageContext: PageContext & { data?: Data }) {
10 | const { store } = pageContext
11 | store.dispatch(initializeTodos(pageContext.data!.todoItemsInitial))
12 | store.dispatch(initializeCount(pageContext.data!.countInitial))
13 |
14 | // Saving KBs: we don't need pageContext.data (we use the store instead)
15 | // - If we don't delete pageContext.data then Vike sends pageContext.data to the client-side
16 | // - This optimization only works if the page is SSR'd: if the page is pre-rendered then don't do this
17 | if (!pageContext.isPrerendering) delete pageContext.data
18 | }
19 |
--------------------------------------------------------------------------------
/examples/redux/pages/index/TodoList.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | import React from 'react'
4 | import { useAppDispatch, useAppSelector } from '../../store/hooks'
5 | import { addTodo, selectTodos } from '../../store/slices/todos'
6 |
7 | export function TodoList() {
8 | const [newTodo, setNewTodo] = useState('')
9 | const dispatch = useAppDispatch()
10 | const todoItems = useAppSelector(selectTodos)
11 | return (
12 | <>
13 | To-Do
14 |
15 | {todoItems.map((todoItem, index) => (
16 | // biome-ignore lint:
17 | {todoItem.text}
18 | ))}
19 |
20 |
21 |
31 |
32 | >
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/examples/redux/store/createStore.ts:
--------------------------------------------------------------------------------
1 | export { createStore }
2 | export type AppStore = ReturnType
3 | export type RootState = ReturnType
4 | export type AppDispatch = AppStore['dispatch']
5 |
6 | import type { PageContext } from 'vike/types'
7 | import { combineReducers, configureStore } from '@reduxjs/toolkit'
8 | import { countReducer } from './slices/count'
9 | import { todosReducer } from './slices/todos'
10 | const reducer = combineReducers({ count: countReducer, todos: todosReducer })
11 |
12 | function createStore(pageContext: PageContext) {
13 | const preloadedState = pageContext.isClientSide ? pageContext.redux?.ssrState : undefined
14 | return configureStore({ reducer, preloadedState })
15 | }
16 |
--------------------------------------------------------------------------------
/examples/redux/store/hooks.ts:
--------------------------------------------------------------------------------
1 | // This file serves as a central hub for re-exporting pre-typed Redux hooks.
2 | import { useDispatch, useSelector, useStore } from 'react-redux'
3 | import type { AppDispatch, AppStore, RootState } from './createStore'
4 |
5 | // Use throughout your app instead of plain `useDispatch` and `useSelector`
6 | export const useAppDispatch = useDispatch.withTypes()
7 | export const useAppSelector = useSelector.withTypes()
8 | export const useAppStore = useStore.withTypes()
9 |
--------------------------------------------------------------------------------
/examples/redux/store/slices/count.ts:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit'
2 | import type { PayloadAction } from '@reduxjs/toolkit'
3 |
4 | const initialState = { countValue: 0 }
5 |
6 | const countSlice = createSlice({
7 | name: 'count',
8 | initialState,
9 | reducers: {
10 | increment: (state) => {
11 | state.countValue += 1
12 | },
13 | decrement: (state) => {
14 | state.countValue -= 1
15 | },
16 | initializeCount: (state, action: PayloadAction) => {
17 | if (state.countValue !== 0) return
18 | state.countValue = action.payload
19 | },
20 | },
21 | selectors: {
22 | selectCount: (state) => state.countValue,
23 | },
24 | })
25 |
26 | export const countReducer = countSlice.reducer
27 | export const { selectCount } = countSlice.selectors
28 | export const { increment, decrement, initializeCount } = countSlice.actions
29 |
--------------------------------------------------------------------------------
/examples/redux/store/slices/todos.ts:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit'
2 | import type { PayloadAction } from '@reduxjs/toolkit'
3 |
4 | type Todo = { text: string }
5 | const initialState = { todoItems: [] as Todo[] }
6 |
7 | const todosSlice = createSlice({
8 | name: 'todos',
9 | initialState,
10 | reducers: {
11 | addTodo: (state, action: PayloadAction) => {
12 | state.todoItems.push({ text: action.payload })
13 | },
14 | initializeTodos: (state, action: PayloadAction) => {
15 | if (state.todoItems.length > 0) return
16 | state.todoItems = action.payload
17 | },
18 | },
19 | selectors: {
20 | selectTodos: (state) => state.todoItems,
21 | },
22 | })
23 |
24 | export const todosReducer = todosSlice.reducer
25 | export const { selectTodos } = todosSlice.selectors
26 | export const { addTodo, initializeTodos } = todosSlice.actions
27 |
--------------------------------------------------------------------------------
/examples/redux/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "ES2020",
5 | "moduleResolution": "Node",
6 | "target": "ES2020",
7 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
8 | "types": ["vite/client"],
9 | "jsx": "react",
10 | "skipLibCheck": true,
11 | "esModuleInterop": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/redux/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import vike from 'vike/plugin'
3 | import { UserConfig } from 'vite'
4 |
5 | export default {
6 | plugins: [react(), vike()],
7 | } satisfies UserConfig
8 |
--------------------------------------------------------------------------------
/examples/zustand/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/examples/zustand/.test-dev.test.ts:
--------------------------------------------------------------------------------
1 | import { testRun } from './.testRun'
2 | testRun('pnpm run dev')
3 |
--------------------------------------------------------------------------------
/examples/zustand/.test-preview.test.ts:
--------------------------------------------------------------------------------
1 | import { testRun } from './.testRun'
2 | testRun('pnpm run preview')
3 |
--------------------------------------------------------------------------------
/examples/zustand/README.md:
--------------------------------------------------------------------------------
1 | Example of using `vike-react-zustand`.
2 |
3 | ```bash
4 | git clone git@github.com:vikejs/vike-react
5 | cd vike-react/examples/zustand/
6 | npm install
7 | npm run dev
8 | ```
9 |
--------------------------------------------------------------------------------
/examples/zustand/components/Counter.tsx:
--------------------------------------------------------------------------------
1 | export { Counter }
2 |
3 | import React from 'react'
4 | import { useCounterStore } from '../store'
5 |
6 | function Counter() {
7 | const { counter, setCounter } = useCounterStore()
8 |
9 | return setCounter(counter + 1)}>Counter {counter}
10 | }
11 |
--------------------------------------------------------------------------------
/examples/zustand/layouts/HeadDefault.tsx:
--------------------------------------------------------------------------------
1 | export default HeadDefault
2 |
3 | import React from 'react'
4 |
5 | function HeadDefault() {
6 | return (
7 | <>
8 |
9 | >
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/examples/zustand/layouts/LayoutDefault.tsx:
--------------------------------------------------------------------------------
1 | export default LayoutDefault
2 |
3 | import './style.css'
4 | import React from 'react'
5 | import logoUrl from '../assets/logo.svg'
6 |
7 | function LayoutDefault({ children }: { children: React.ReactNode }) {
8 | return (
9 |
16 |
17 |
18 |
19 | {children}
20 |
21 | )
22 | }
23 |
24 | function Sidebar({ children }: { children: React.ReactNode }) {
25 | return (
26 |
39 | )
40 | }
41 |
42 | function Content({ children }: { children: React.ReactNode }) {
43 | return (
44 |
45 |
53 | {children}
54 |
55 |
56 | )
57 | }
58 |
59 | function Logo() {
60 | return (
61 |
71 | )
72 | }
73 |
--------------------------------------------------------------------------------
/examples/zustand/layouts/style.css:
--------------------------------------------------------------------------------
1 | /* Links */
2 | a {
3 | text-decoration: none;
4 | }
5 | #sidebar a {
6 | padding: 2px 10px;
7 | margin-left: -10px;
8 | }
9 | #sidebar a.is-active {
10 | background-color: #eee;
11 | }
12 |
13 | /* Reset */
14 | body {
15 | margin: 0;
16 | font-family: sans-serif;
17 | }
18 | * {
19 | box-sizing: border-box;
20 | }
21 |
22 | /* Page Transition Anmiation */
23 | #page-content {
24 | opacity: 1;
25 | transition: opacity 0.3s ease-in-out;
26 | }
27 | body.page-is-transitioning #page-content {
28 | opacity: 0;
29 | }
30 |
--------------------------------------------------------------------------------
/examples/zustand/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "dev": "vike dev",
4 | "build": "vike build",
5 | "preview": "vike build && vike preview",
6 | "test": "tsc --noEmit"
7 | },
8 | "dependencies": {
9 | "@types/react": "^19.0.10",
10 | "@types/react-dom": "^19.0.4",
11 | "@vitejs/plugin-react": "^4.3.4",
12 | "immer": "^10.0.3",
13 | "react": "^19.0.0",
14 | "react-dom": "^19.0.0",
15 | "typescript": "^5.8.3",
16 | "vike": "^0.4.223",
17 | "vike-react": "0.6.4",
18 | "vike-react-zustand": "0.1.0",
19 | "vite": "^6.2.5",
20 | "zustand": "^5.0.3"
21 | },
22 | "type": "module"
23 | }
24 |
--------------------------------------------------------------------------------
/examples/zustand/pages/+Layout.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export const Layout = ({ children }: { children: React.ReactNode }) => {
4 | return (
5 |
6 |
7 | Welcome About
8 |
9 | {children}
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/examples/zustand/pages/+config.ts:
--------------------------------------------------------------------------------
1 | export { config }
2 |
3 | import type { Config } from 'vike/types'
4 | import vikeReact from 'vike-react/config'
5 | import vikeReactZustand from 'vike-react-zustand/config'
6 |
7 | // Default configs (can be overridden by pages)
8 | const config = {
9 | //
10 | title: 'My Vike + React App',
11 | // https://vike.dev/stream
12 | stream: true,
13 | // https://vike.dev/ssr - this line can be removed since `true` is the default
14 | ssr: true,
15 | // https://vike.dev/extends
16 | extends: [vikeReact, vikeReactZustand],
17 | } satisfies Config
18 |
--------------------------------------------------------------------------------
/examples/zustand/pages/_error/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page
2 |
3 | import React from 'react'
4 |
5 | function Page({ is404, errorInfo }: { is404: boolean; errorInfo?: string }) {
6 | if (is404) {
7 | return (
8 | <>
9 | 404 Page Not Found
10 | This page could not be found.
11 | {errorInfo}
12 | >
13 | )
14 | } else {
15 | return (
16 | <>
17 | 500 Internal Server Error
18 | Something went wrong.
19 | >
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/zustand/pages/about/+Page.tsx:
--------------------------------------------------------------------------------
1 | export default Page
2 |
3 | import React from 'react'
4 | import { Counter } from '../../components/Counter'
5 |
6 | function Page() {
7 | return (
8 | <>
9 | <>
10 | About
11 | The counter value is the same as on the Welcome page.
12 |
13 | >
14 | >
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/examples/zustand/pages/index/+Page.tsx:
--------------------------------------------------------------------------------
1 | export { Page }
2 |
3 | import React from 'react'
4 | import { Counter } from '../../components/Counter'
5 | import { TodoList } from './TodoList'
6 |
7 | function Page() {
8 | return (
9 | <>
10 | Welcome
11 | This page is:
12 |
13 | Rendered to HTML.
14 |
15 | Interactive while loading.
16 |
17 |
18 |
19 | >
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/examples/zustand/pages/index/+data.ts:
--------------------------------------------------------------------------------
1 | // Environment: server
2 | export { data }
3 | export type Data = Awaited>
4 |
5 | import type { PageContextServer } from 'vike/types'
6 |
7 | async function data(pageContext: PageContextServer) {
8 | const todoItemsInitial = await fetchTodosInit()
9 | return { todoItemsInitial }
10 | }
11 |
12 | // Pretending the list is fetched over the network
13 | async function fetchTodosInit() {
14 | return [
15 | //
16 | { text: 'Buy apples' },
17 | { text: `Update Node.js ${process.version} to latest version` },
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/examples/zustand/pages/index/TodoList.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | import React from 'react'
4 | import { useTodoStore } from '../../store'
5 | import { useStoreVanilla } from 'vike-react-zustand'
6 |
7 | export function TodoList() {
8 | const [newTodo, setNewTodo] = useState('')
9 | const { todoItems, addTodo } = useTodoStore()
10 | const storeVanilla = useStoreVanilla(useTodoStore)
11 | useEffect(
12 | () =>
13 | storeVanilla.subscribe((state) => {
14 | console.log(JSON.stringify(state.todoItems))
15 | }),
16 | [],
17 | )
18 |
19 | return (
20 | <>
21 | To-Do
22 |
23 | {todoItems.map((todoItem, index) => (
24 | // biome-ignore lint:
25 | {todoItem.text}
26 | ))}
27 |
28 |
29 |
39 |
40 | >
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/examples/zustand/store.ts:
--------------------------------------------------------------------------------
1 | export { useCounterStore }
2 | export { useTodoStore }
3 |
4 | import { create, withPageContext } from 'vike-react-zustand'
5 | import { immer } from 'zustand/middleware/immer'
6 | import type { Data } from './pages/index/+data'
7 |
8 | interface CounterStore {
9 | counter: number
10 | setCounter: (value: number) => void
11 | }
12 | const useCounterStore = create()(
13 | immer((set, get) => ({
14 | setCounter(value) {
15 | set((state) => {
16 | state.counter = value
17 | })
18 | },
19 | counter: Math.floor(10000 * Math.random()),
20 | })),
21 | )
22 |
23 | type Todo = { text: string }
24 | interface TodoStore {
25 | todoItems: Todo[]
26 | addTodo: (todo: Todo) => void
27 | }
28 | const useTodoStore = create()(
29 | withPageContext((pageContext) =>
30 | immer((set, get) => ({
31 | todoItems: (pageContext.data as Data).todoItemsInitial,
32 | addTodo(todo) {
33 | set((state) => {
34 | state.todoItems.push(todo)
35 | })
36 | },
37 | })),
38 | ),
39 | )
40 |
--------------------------------------------------------------------------------
/examples/zustand/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "ES2020",
5 | "moduleResolution": "Node",
6 | "target": "ES2020",
7 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
8 | "types": ["vite/client"],
9 | "jsx": "react",
10 | "skipLibCheck": true,
11 | "esModuleInterop": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/zustand/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import vike from 'vike/plugin'
3 | import type { UserConfig } from 'vite'
4 |
5 | export default {
6 | plugins: [react(), vike()],
7 | } satisfies UserConfig
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "========= Build": "",
4 | "build": "pnpm --recursive --filter {packages/*} run build",
5 | "========= Dev": "",
6 | "dev": "cd ./packages/vike-react/ && pnpm run dev",
7 | "========= Test": "",
8 | "test": "pnpm run test:units && pnpm run test:e2e && pnpm run test:types",
9 | "test:e2e": "test-e2e",
10 | "test:units": "pnpm --recursive --sequential --filter {packages/*} run test",
11 | "test:types": "test-types",
12 | "========= Formatting": "",
13 | "format": "pnpm run format:biome",
14 | "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",
15 | "format:biome": "biome format --write .",
16 | "format:check": "biome format . || (echo 'Fix formatting by running `$ pnpm run -w format`.' && exit 1)",
17 | "========= Release": "",
18 | "release": "cd ./packages/vike-react/ && pnpm run release",
19 | "release:minor": "cd ./packages/vike-react/ && pnpm run release:minor",
20 | "release:commit": "cd ./packages/vike-react/ && pnpm run release:commit",
21 | "========= Reset": "",
22 | "reset": "git clean -Xdf && pnpm install && pnpm run build",
23 | "========= Only allow pnpm; forbid yarn & npm": "",
24 | "preinstall": "npx only-allow pnpm"
25 | },
26 | "pnpm": {
27 | "overrides": {
28 | "vike-react": "link:./packages/vike-react/",
29 | "vike-react-query": "link:./packages/vike-react-query/",
30 | "vike-react-apollo": "link:./packages/vike-react-apollo/",
31 | "vike-react-chakra": "link:./packages/vike-react-chakra/",
32 | "vike-react-antd": "link:./packages/vike-react-antd/",
33 | "vike-react-styled-components": "link:./packages/vike-react-styled-components/",
34 | "vike-react-styled-jsx": "link:./packages/vike-react-styled-jsx/",
35 | "vike-react-redux": "link:./packages/vike-react-redux/",
36 | "vike-react-zustand": "link:./packages/vike-react-zustand"
37 | }
38 | },
39 | "devDependencies": {
40 | "@biomejs/biome": "^1.8.3",
41 | "@brillout/test-e2e": "^0.6.10",
42 | "@brillout/test-types": "^0.1.15",
43 | "playwright": "^1.45.0",
44 | "prettier": "^3.2.5"
45 | },
46 | "packageManager": "pnpm@9.4.0"
47 | }
48 |
--------------------------------------------------------------------------------
/packages/vike-react-antd/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/packages/vike-react-antd/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [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)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * simplify condition checks ([d67da36](https://github.com/vikejs/vike-react/commit/d67da3646e13f0b4c7493ff8c193e50c6f5ca15e))
7 | * stop overriding Wrapper meta ([ddcab7c](https://github.com/vikejs/vike-react/commit/ddcab7c21742b3701909b3698dca21b89c3dfebc)) ([f8452fc](https://github.com/vikejs/vike-react/commit/f8452fc1d3750693b5fd1556da64fceec930e21c))
8 |
9 |
10 |
11 | ## [1.0.1](https://github.com/vikejs/vike-react/compare/vike-react-antd@1.0.0...vike-react-antd@1.0.1) (2024-12-23)
12 |
13 |
14 | ### Bug Fixes
15 |
16 | * move `pageContext.config.antd.cache` to `pageContext.antd.cache` ([a2a533d](https://github.com/vikejs/vike-react/commit/a2a533dab959897c213770d019942b46438a517c))
17 |
18 |
19 |
20 | # 1.0.0 (2024-11-30)
21 |
22 |
23 | ### Features
24 |
25 | * new extension `vike-react-antd` ([636fb8a](https://github.com/vikejs/vike-react/commit/636fb8ad6abfadf485f647cb68ea7f77ede216cf))
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/packages/vike-react-antd/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://www.npmjs.com/package/vike-react-antd)
4 |
5 | # `vike-react-antd`
6 |
7 | Integrates [Ant Design](https://ant.design) into your [`vike-react`](https://vike.dev/vike-react) app.
8 |
9 | [Installation](#installation)
10 | [Settings](#settings)
11 | [Version history](https://github.com/vikejs/vike-react/blob/main/packages/vike-react-antd/CHANGELOG.md)
12 | [What it does](#what-it-does)
13 | [See also](#see-also)
14 |
15 |
16 |
17 | ## Installation
18 |
19 | 1. `npm install vike-react-antd antd @ant-design/cssinjs`
20 | 2. Extend `+config.js`:
21 | ```js
22 | // pages/+config.js
23 |
24 | import vikeReact from "vike-react/config"
25 | import vikeReactAntd from "vike-react-antd/config"
26 |
27 | export default {
28 | // ...
29 | extends: [vikeReact, vikeReactAntd]
30 | }
31 | ```
32 | 3. You can now use Ant Design in any of your components.
33 | ```jsx
34 | import { Button, Flex } from "antd";
35 |
36 | function SomeComponent() {
37 | return (
38 |
39 | Primary Button
40 | Default Button
41 |
42 | )
43 | }
44 | ```
45 |
46 | > [!NOTE]
47 | > The `vike-react-antd` extension requires [`vike-react`](https://vike.dev/vike-react).
48 |
49 |
50 |
51 | ## Settings
52 |
53 | `vike-react-antd` provides a configuration `+antd` for customizing Ant Design [Style Compatibility](https://ant.design/docs/react/compatible-style).
54 |
55 | ```ts
56 | // pages/+antd.ts
57 | export { antd }
58 |
59 | import { legacyLogicalPropertiesTransformer, px2remTransformer, type StyleProviderProps } from "@ant-design/cssinjs"
60 |
61 | const px2rem = px2remTransformer({
62 | rootValue: 32, // 32px = 1rem; @default 16
63 | })
64 |
65 | const antd: Omit = {
66 | hashPriority: "high",
67 | layer: true,
68 | transformers: [legacyLogicalPropertiesTransformer, px2rem],
69 | }
70 | ```
71 |
72 | You can remove the `vike-react-antd` integration from [some of your pages](https://vike.dev/config#inheritance):
73 |
74 | ```js
75 | // pages/about/+antd.js
76 |
77 | export const antd = null
78 | ```
79 |
80 | For full customization consider [ejecting](https://vike.dev/eject).
81 |
82 | > [!NOTE]
83 | > Consider making a [Pull Request before ejecting](https://vike.dev/eject#when-to-eject).
84 |
85 |
86 |
87 | ## What it does
88 |
89 | The `vike-react-antd` extension allows you to use Ant Design without [FOUC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content).
90 |
91 | It collects the page's styles during SSR and injects them in the HTML, ensuring that styles are applied early (before even JavaScript starts loading).
92 |
93 | You can learn more at:
94 | - [Vike > CSS-in-JS > Collect styles](https://vike.dev/css-in-js#collect-styles)
95 | - [Antd Design > Server Side Rendering](https://ant.design/docs/react/server-side-rendering)
96 |
97 | For more details, have a look at the source code of `vike-react-styled-jsx` (which is small).
98 |
99 |
100 |
101 | ## See also
102 |
103 | - [Vike Docs > Ant Design](https://vike.dev/antd)
104 | - [Vike Docs > CSS-in-JS](https://vike.dev/css-in-js)
105 | - [Antd Design](https://ant.design)
106 |
--------------------------------------------------------------------------------
/packages/vike-react-antd/Wrapper.server.tsx:
--------------------------------------------------------------------------------
1 | export { Wrapper }
2 |
3 | import React, { type ReactNode } from 'react'
4 | import { StyleProvider } from '@ant-design/cssinjs'
5 | import { usePageContext } from 'vike-react/usePageContext'
6 |
7 | function Wrapper({ children }: { children: ReactNode }) {
8 | const pageContext = usePageContext()
9 | const { antd } = pageContext.config
10 |
11 | if (antd === null) {
12 | return <>{children}>
13 | }
14 |
15 | return (
16 |
17 | {children}
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/packages/vike-react-antd/config.ts:
--------------------------------------------------------------------------------
1 | export { config as default }
2 |
3 | import type { StyleProviderProps } from '@ant-design/cssinjs'
4 | import type { Config } from 'vike/types'
5 |
6 | const config = {
7 | name: 'vike-react-antd',
8 | require: {
9 | vike: '>=0.4.211',
10 | 'vike-react': '>=0.4.13',
11 | },
12 | onAfterRenderHtml: 'import:vike-react-antd/__internal/onAfterRenderHtml:onAfterRenderHtml',
13 | onBeforeRenderHtml: 'import:vike-react-antd/__internal/onBeforeRenderHtml:onBeforeRenderHtml',
14 | Wrapper: 'import:vike-react-antd/__internal/Wrapper:Wrapper',
15 | meta: {
16 | antd: {
17 | env: {
18 | server: true,
19 | client: false,
20 | },
21 | },
22 | },
23 | } satisfies Config
24 |
25 | declare global {
26 | namespace Vike {
27 | interface PageContext {
28 | antd?: {
29 | cache?: StyleProviderProps['cache']
30 | }
31 | }
32 | interface Config {
33 | antd?: null | Omit
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/vike-react-antd/onAfterRenderHtml.ts:
--------------------------------------------------------------------------------
1 | export { onAfterRenderHtml }
2 |
3 | import React from 'react'
4 | import { extractStyle } from '@ant-design/cssinjs'
5 | import { useConfig } from 'vike-react/useConfig'
6 | import type { PageContext } from 'vike/types'
7 |
8 | function onAfterRenderHtml(pageContext: PageContext) {
9 | const config = useConfig()
10 | const cache = pageContext.antd?.cache
11 |
12 | if (cache) {
13 | const styleTag = React.createElement('style', {
14 | id: 'antd-cssinjs',
15 | dangerouslySetInnerHTML: {
16 | __html: extractStyle(cache, true),
17 | },
18 | })
19 | config({
20 | Head: styleTag,
21 | })
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/vike-react-antd/onBeforeRenderHtml.ts:
--------------------------------------------------------------------------------
1 | export { onBeforeRenderHtml }
2 |
3 | import { createCache } from '@ant-design/cssinjs'
4 | import type { PageContext } from 'vike/types'
5 |
6 | function onBeforeRenderHtml(pageContext: PageContext) {
7 | if (pageContext.config.antd !== null) {
8 | pageContext.antd = {
9 | cache: createCache(),
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/vike-react-antd/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vike-react-antd",
3 | "version": "1.0.2",
4 | "type": "module",
5 | "exports": {
6 | "./config": "./dist/config.js",
7 | "./__internal/onAfterRenderHtml": "./dist/onAfterRenderHtml.js",
8 | "./__internal/onBeforeRenderHtml": "./dist/onBeforeRenderHtml.js",
9 | "./__internal/Wrapper": "./dist/Wrapper.server.js"
10 | },
11 | "scripts": {
12 | "dev": "tsc --watch",
13 | "build": "rimraf dist/ && tsc",
14 | "release": "release-me patch",
15 | "release:minor": "release-me minor",
16 | "release:major": "release-me major",
17 | "release:commit": "release-me commit"
18 | },
19 | "peerDependencies": {
20 | "@ant-design/cssinjs": ">=1.22",
21 | "antd": ">=5",
22 | "react": ">=18",
23 | "vike-react": ">=0.4.13"
24 | },
25 | "devDependencies": {
26 | "@ant-design/cssinjs": "^1.22.1",
27 | "@brillout/release-me": "^0.4.2",
28 | "@types/react": "^19.0.10",
29 | "antd": "^5.22.5",
30 | "react": "^19.0.0",
31 | "rimraf": "^5.0.5",
32 | "typescript": "^5.8.3",
33 | "vike": "^0.4.230",
34 | "vike-react": "0.6.4",
35 | "vite": "^6.2.5"
36 | },
37 | "typesVersions": {
38 | "*": {
39 | "config": [
40 | "dist/config.d.ts"
41 | ],
42 | "__internal/onAfterRenderHtml": [
43 | "dist/onAfterRenderHtml.d.ts"
44 | ],
45 | "__internal/onBeforeRenderHtml": [
46 | "dist/onBeforeRenderHtml.d.ts"
47 | ],
48 | "__internal/Wrapper": [
49 | "dist/Wrapper.server.d.ts"
50 | ]
51 | }
52 | },
53 | "files": [
54 | "dist"
55 | ],
56 | "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-antd",
57 | "license": "MIT"
58 | }
59 |
--------------------------------------------------------------------------------
/packages/vike-react-antd/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "target": "ESNext",
5 | "module": "ESNext",
6 | "moduleResolution": "Bundler",
7 | "jsx": "react",
8 | "outDir": "./dist/",
9 | "skipLibCheck": true,
10 | "types": ["vike-react"],
11 | // Strictness
12 | "strict": true,
13 | "noUncheckedIndexedAccess": true,
14 | "noImplicitAny": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/vike-react-apollo/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/packages/vike-react-apollo/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [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)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * update +stream usage ([#175](https://github.com/vikejs/vike-react/issues/175)) ([7a3d1d6](https://github.com/vikejs/vike-react/commit/7a3d1d601f0ff2ff45409d92b3226f544eaf24c7))
7 |
8 |
9 |
10 | ## [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)
11 |
12 |
13 | ### Bug Fixes
14 |
15 | * withFallback bug ([aa51a93](https://github.com/vikejs/vike-react/commit/aa51a93d40cbd5fc04225a56d2be546b794c1fb2))
16 |
17 |
18 |
19 | # 0.1.0 (2024-07-16)
20 |
21 |
22 | ### Features
23 |
24 | * add vike-react-apollo ([#134](https://github.com/vikejs/vike-react/issues/134)) ([95bc323](https://github.com/vikejs/vike-react/commit/95bc323c696091bae908c72e38a010f27eff22e0))
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/packages/vike-react-apollo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vike-react-apollo",
3 | "version": "0.1.2",
4 | "type": "module",
5 | "main": "dist/index.js",
6 | "typings": "dist/index.js",
7 | "exports": {
8 | ".": "./dist/index.js",
9 | "./config": "./dist/integration/+config.js",
10 | "./__internal/integration/Wrapper": "./dist/integration/Wrapper.js"
11 | },
12 | "scripts": {
13 | "dev": "tsc --watch",
14 | "build": "rimraf dist/ && tsc",
15 | "release": "release-me patch",
16 | "release:minor": "release-me minor",
17 | "release:commit": "release-me commit"
18 | },
19 | "peerDependencies": {
20 | "@apollo/client": ">=3.0.0",
21 | "graphql": ">=16.0.0",
22 | "@apollo/client-react-streaming": ">=0.11.0",
23 | "react": ">=18.0.0",
24 | "react-dom": ">=18.0.0",
25 | "react-streaming": ">=0.3.41",
26 | "vike-react": ">=0.6.4"
27 | },
28 | "devDependencies": {
29 | "@brillout/release-me": "^0.4.2",
30 | "@apollo/client": "^3.10.8",
31 | "@apollo/client-react-streaming": "^0.11.2",
32 | "graphql": "^16.9.0",
33 | "@types/node": "^20.11.17",
34 | "@types/react": "^19.0.10",
35 | "react": "^19.0.0",
36 | "react-dom": "^19.0.0",
37 | "@types/react-dom": "^19.0.4",
38 | "react-streaming": "^0.4.2",
39 | "rimraf": "^5.0.5",
40 | "typescript": "^5.8.3",
41 | "vike": "^0.4.230",
42 | "vike-react": "0.6.4",
43 | "vite": "^6.2.5"
44 | },
45 | "dependencies": {
46 | "react-error-boundary": "^4.0.12"
47 | },
48 | "typesVersions": {
49 | "*": {
50 | "config": [
51 | "dist/integration/+config.d.ts"
52 | ],
53 | "__internal/integration/Wrapper": [
54 | "dist/integration/Wrapper.d.ts"
55 | ]
56 | }
57 | },
58 | "files": [
59 | "dist"
60 | ],
61 | "repository": "github:vikejs/vike-react",
62 | "license": "MIT"
63 | }
64 |
--------------------------------------------------------------------------------
/packages/vike-react-apollo/src/index.ts:
--------------------------------------------------------------------------------
1 | export { withFallback } from './withFallback.js'
2 |
--------------------------------------------------------------------------------
/packages/vike-react-apollo/src/integration/+config.ts:
--------------------------------------------------------------------------------
1 | export { config as default }
2 |
3 | import type { Config } from 'vike/types'
4 | import 'vike-react/config' // Needed for declaration merging of Config
5 | import type { ApolloClient } from '@apollo/client-react-streaming'
6 |
7 | const config = {
8 | name: 'vike-react-apollo',
9 | require: {
10 | 'vike-react': '>=0.6.4',
11 | },
12 | Wrapper: 'import:vike-react-apollo/__internal/integration/Wrapper:Wrapper',
13 | stream: { require: true },
14 | meta: {
15 | ApolloClient: {
16 | env: {
17 | server: true,
18 | client: true,
19 | },
20 | },
21 | },
22 | } satisfies Config
23 |
24 | declare global {
25 | namespace Vike {
26 | interface Config {
27 | ApolloClient?: (pageContext: PageContext) => ApolloClient
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/vike-react-apollo/src/integration/Transport.tsx:
--------------------------------------------------------------------------------
1 | import { WrapApolloProvider } from '@apollo/client-react-streaming'
2 | import { buildManualDataTransport } from '@apollo/client-react-streaming/manual-transport'
3 | import { useStream } from 'react-streaming'
4 | import { renderToString } from 'react-dom/server'
5 | import React from 'react'
6 |
7 | export const WrappedApolloProvider = WrapApolloProvider(
8 | buildManualDataTransport({
9 | useInsertHtml() {
10 | const stream = useStream()
11 | if (!stream) {
12 | return () => {}
13 | }
14 | return (callback: () => React.ReactNode) => {
15 | stream.injectToStream(
16 | // https://github.com/apollographql/apollo-client-nextjs/issues/325
17 | (async () => renderToString(await callback()))(),
18 | )
19 | }
20 | },
21 | }),
22 | )
23 |
--------------------------------------------------------------------------------
/packages/vike-react-apollo/src/integration/Wrapper.tsx:
--------------------------------------------------------------------------------
1 | export { Wrapper }
2 |
3 | import React, { type ReactNode } from 'react'
4 | import { usePageContext } from 'vike-react/usePageContext'
5 | import { assertUsage } from '../utils/assert.js'
6 | import { WrappedApolloProvider } from './Transport.js'
7 |
8 | function Wrapper({ children }: { children: ReactNode }) {
9 | const pageContext = usePageContext()
10 | const { ApolloClient: getApolloClient } = pageContext.config
11 | assertUsage(getApolloClient, 'Setting +ApolloClient is required')
12 | return getApolloClient(pageContext)}>{children}
13 | }
14 |
--------------------------------------------------------------------------------
/packages/vike-react-apollo/src/utils/assert.ts:
--------------------------------------------------------------------------------
1 | export { assertUsage }
2 |
3 | function assertUsage(condition: unknown, message: string): asserts condition {
4 | if (condition) return
5 | throw new Error('Wrong usage: ' + message)
6 | }
7 |
--------------------------------------------------------------------------------
/packages/vike-react-apollo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | // Resolution
5 | "target": "ES2020",
6 | "module": "Node16",
7 | "moduleResolution": "Node16",
8 | // Libs
9 | "lib": ["ES2021", "DOM", "DOM.Iterable"],
10 | "types": ["vite/client"],
11 | // Strictness
12 | "strict": true,
13 | "noUncheckedIndexedAccess": true,
14 | "noImplicitAny": true,
15 | // Output
16 | "declaration": true,
17 | "noEmitOnError": false,
18 | "rootDir": "./src/",
19 | // Misc
20 | "esModuleInterop": true,
21 | "skipLibCheck": true,
22 | "jsx": "react"
23 | },
24 | "include": ["./src"]
25 | }
26 |
--------------------------------------------------------------------------------
/packages/vike-react-chakra/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/packages/vike-react-chakra/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [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)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * add eject.config.js ([0070495](https://github.com/vikejs/vike-react/commit/00704957fcf374ad0c7ebb0645a36b8d2035d2d2))
7 |
8 |
9 |
10 | # 1.0.0 (2024-11-18)
11 |
12 | ### Features
13 |
14 | * new extension vike-react-chakra ([#151](https://github.com/vikejs/vike-react/issues/151)) ([0850332](https://github.com/vikejs/vike-react/pull/151/commits/0850332bcc570145b6d548fe1add73427788b0bd))
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/packages/vike-react-chakra/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://www.npmjs.com/package/vike-react-chakra)
4 |
5 | # `vike-react-chakra`
6 |
7 | Integrates [Chakra UI](https://www.chakra-ui.com/) into your [`vike-react`](https://vike.dev/vike-react) app.
8 |
9 | [Installation](#installation)
10 | [Settings](#settings)
11 | [Version history](https://github.com/vikejs/vike-react/blob/main/packages/vike-react-chakra/CHANGELOG.md)
12 | [See also](#see-also)
13 |
14 |
15 |
16 | ## Installation
17 |
18 | 1. `npm install vike-react-chakra @chakra-ui/react @emotion/react`
19 | 2. Extend `+config.js`:
20 | ```js
21 | // pages/+config.js
22 |
23 | import vikeReact from 'vike-react/config'
24 | import vikeReactChakra from 'vike-react-chakra/config'
25 |
26 | export default {
27 | // ...
28 | extends: [vikeReact, vikeReactChakra]
29 | }
30 | ```
31 | 3. You can now use Chakra in any of your components.
32 | ```jsx
33 | import { HStack, Button } from '@chakra-ui/react'
34 |
35 | function SomeComponent() {
36 | return (
37 |
38 | Click me
39 | Click me
40 |
41 | )
42 | }
43 | ```
44 |
45 | > [!NOTE]
46 | > The `vike-react-chakra` extension requires [`vike-react`](https://vike.dev/vike-react).
47 |
48 |
49 |
50 | ## Settings
51 |
52 | `vike-react-chakra` provides a configuration `+chakra` for setting the theme system and locale.
53 |
54 | ```js
55 | // pages/+chakra.js
56 |
57 | export { chakra }
58 |
59 | import { createSystem, defaultConfig, defineConfig } from '@chakra-ui/react'
60 |
61 | const customConfig = defineConfig({
62 | globalCss: {
63 | "html, body": {
64 | margin: 0,
65 | padding: 0
66 | }
67 | }
68 | })
69 |
70 | const system = createSystem(defaultConfig, customConfig)
71 |
72 | const chakra = {
73 | system,
74 | locale: "fr-FR"
75 | }
76 | ```
77 |
78 | You can remove Chakra from [some of your pages](https://vike.dev/config#inheritance):
79 |
80 | ```js
81 | // pages/about/+chakra.js
82 |
83 | export const chakra = null
84 | ```
85 |
86 | For full customization consider [ejecting](https://vike.dev/eject).
87 |
88 | > [!NOTE]
89 | > Consider making a [Pull Request before ejecting](https://vike.dev/eject#when-to-eject).
90 |
91 |
92 |
93 | ## See also
94 |
95 | - [Vike Docs > Chakra UI](https://vike.dev/chakra)
96 |
--------------------------------------------------------------------------------
/packages/vike-react-chakra/Wrapper.tsx:
--------------------------------------------------------------------------------
1 | export { Wrapper }
2 |
3 | import React, { type ReactNode } from 'react'
4 | import { ChakraProvider, LocaleProvider, defaultSystem } from '@chakra-ui/react'
5 | import { usePageContext } from 'vike-react/usePageContext'
6 |
7 | function Wrapper({ children }: { children: ReactNode }) {
8 | const pageContext = usePageContext()
9 | const { chakra } = pageContext.config
10 |
11 | if (chakra === null) {
12 | return <>{children}>
13 | }
14 |
15 | return (
16 |
17 | {children}
18 |
19 | )
20 | }
21 |
22 | function ChakraLocaleProvider({ locale, children }: { locale?: string; children: ReactNode }) {
23 | if (locale) {
24 | return {children}
25 | }
26 | return <>{children}>
27 | }
28 |
--------------------------------------------------------------------------------
/packages/vike-react-chakra/config.ts:
--------------------------------------------------------------------------------
1 | export { config as default }
2 |
3 | import type { LocaleProviderProps, SystemContext } from '@chakra-ui/react'
4 | import type { Config } from 'vike/types'
5 |
6 | const config = {
7 | name: 'vike-react-chakra',
8 | require: {
9 | vike: '>=0.4.203',
10 | 'vike-react': '>=0.4.13',
11 | },
12 | Wrapper: 'import:vike-react-chakra/__internal/Wrapper:Wrapper',
13 | meta: {
14 | chakra: {
15 | env: {
16 | server: true,
17 | client: true,
18 | },
19 | },
20 | },
21 | } satisfies Config
22 |
23 | declare global {
24 | namespace Vike {
25 | interface Config {
26 | chakra?: null | {
27 | system?: SystemContext
28 | locale?: LocaleProviderProps['locale']
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/vike-react-chakra/eject.config.js:
--------------------------------------------------------------------------------
1 | // This is work-in-progress, see:
2 | // - https://github.com/brillout/playground_eject-vike-react-chakra
3 | // - https://github.com/snake-py/eject/issues/4#issuecomment-2506217514
4 | export const config = {
5 | files: 'Wrapper.tsx',
6 | operations: ['mv Wrapper.tsx pages/+Wrapper.tsx'],
7 | }
8 |
--------------------------------------------------------------------------------
/packages/vike-react-chakra/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vike-react-chakra",
3 | "version": "1.0.1",
4 | "type": "module",
5 | "exports": {
6 | "./config": "./dist/config.js",
7 | "./__internal/Wrapper": "./dist/Wrapper.js"
8 | },
9 | "scripts": {
10 | "dev": "tsc --watch",
11 | "build": "rimraf dist/ && tsc",
12 | "release": "release-me patch",
13 | "release:minor": "release-me minor",
14 | "release:major": "release-me major",
15 | "release:commit": "release-me commit"
16 | },
17 | "peerDependencies": {
18 | "@chakra-ui/react": ">=3",
19 | "@emotion/react": ">=11",
20 | "react": ">=18",
21 | "vike-react": ">=0.4.13"
22 | },
23 | "devDependencies": {
24 | "@brillout/release-me": "^0.4.2",
25 | "@chakra-ui/react": "^3.0.2",
26 | "@emotion/react": "^11.13.3",
27 | "@types/react": "^19.0.10",
28 | "react": "^19.0.0",
29 | "rimraf": "^5.0.5",
30 | "typescript": "^5.8.3",
31 | "vike": "^0.4.230",
32 | "vike-react": "0.6.4",
33 | "vite": "^6.2.5"
34 | },
35 | "typesVersions": {
36 | "*": {
37 | "config": [
38 | "dist/config.d.ts"
39 | ],
40 | "__internal/Wrapper": [
41 | "dist/Wrapper.d.ts"
42 | ]
43 | }
44 | },
45 | "files": [
46 | "dist"
47 | ],
48 | "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-chakra",
49 | "license": "MIT"
50 | }
51 |
--------------------------------------------------------------------------------
/packages/vike-react-chakra/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "target": "ESNext",
5 | "module": "ESNext",
6 | "moduleResolution": "Bundler",
7 | "jsx": "react",
8 | "outDir": "./dist/",
9 | "skipLibCheck": true,
10 | "types": ["vike-react"],
11 | // Strictness
12 | "strict": true,
13 | "noUncheckedIndexedAccess": true,
14 | "noImplicitAny": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/vike-react-query/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/packages/vike-react-query/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vike-react-query",
3 | "version": "0.1.4",
4 | "type": "module",
5 | "main": "dist/index.js",
6 | "typings": "dist/index.js",
7 | "exports": {
8 | ".": "./dist/index.js",
9 | "./config": "./dist/integration/+config.js",
10 | "./__internal/integration/Wrapper": "./dist/integration/Wrapper.js",
11 | "./__internal/integration/FallbackErrorBoundary": "./dist/integration/FallbackErrorBoundary.js"
12 | },
13 | "scripts": {
14 | "dev": "tsc --watch",
15 | "build": "rimraf dist/ && tsc",
16 | "release": "release-me patch",
17 | "release:minor": "release-me minor",
18 | "release:commit": "release-me commit",
19 | "test": "vitest run"
20 | },
21 | "peerDependencies": {
22 | "@tanstack/react-query": ">=5.0.0",
23 | "react": ">=18.0.0",
24 | "react-streaming": ">=0.3.42",
25 | "vike-react": ">=0.6.4"
26 | },
27 | "devDependencies": {
28 | "@brillout/release-me": "^0.4.2",
29 | "@tanstack/react-query": "^5.20.1",
30 | "@testing-library/react": "^14.2.1",
31 | "@types/node": "^20.11.17",
32 | "@types/react": "^19.0.10",
33 | "jsdom": "^24.0.0",
34 | "react": "^19.0.0",
35 | "react-streaming": "^0.4.2",
36 | "rimraf": "^5.0.5",
37 | "typescript": "^5.8.3",
38 | "vike": "^0.4.230",
39 | "vike-react": "0.6.4",
40 | "vite": "^6.2.5",
41 | "vitest": "^1.2.2"
42 | },
43 | "dependencies": {
44 | "devalue": "^4.3.2",
45 | "react-error-boundary": "^4.0.12"
46 | },
47 | "typesVersions": {
48 | "*": {
49 | "config": [
50 | "dist/integration/+config.d.ts"
51 | ],
52 | "__internal/integration/Wrapper": [
53 | "dist/integration/Wrapper.d.ts"
54 | ]
55 | }
56 | },
57 | "files": [
58 | "dist"
59 | ],
60 | "repository": "github:vikejs/vike-react",
61 | "license": "MIT"
62 | }
63 |
--------------------------------------------------------------------------------
/packages/vike-react-query/src/index.ts:
--------------------------------------------------------------------------------
1 | export { withFallback } from './withFallback.js'
2 |
--------------------------------------------------------------------------------
/packages/vike-react-query/src/integration/+config.ts:
--------------------------------------------------------------------------------
1 | export { config as default }
2 |
3 | import type { QueryClientConfig } from '@tanstack/react-query'
4 | import type { ReactNode } from 'react'
5 | import type { Config, ImportString } from 'vike/types'
6 | import 'vike-react/config' // Needed for declaration merging of Config
7 |
8 | const config = {
9 | name: 'vike-react-query',
10 | require: {
11 | 'vike-react': '>=0.6.4',
12 | },
13 | queryClientConfig: undefined,
14 | Wrapper: 'import:vike-react-query/__internal/integration/Wrapper:Wrapper',
15 | FallbackErrorBoundary: 'import:vike-react-query/__internal/integration/FallbackErrorBoundary:FallbackErrorBoundary',
16 | stream: { require: true },
17 | meta: {
18 | queryClientConfig: {
19 | env: {
20 | server: true,
21 | client: true,
22 | },
23 | },
24 | FallbackErrorBoundary: {
25 | env: {
26 | server: true,
27 | client: true,
28 | },
29 | },
30 | },
31 | } satisfies Config
32 |
33 | declare global {
34 | namespace Vike {
35 | interface Config {
36 | queryClientConfig?: QueryClientConfig | ((pageContext: PageContext) => QueryClientConfig)
37 | FallbackErrorBoundary?: ((props: { children: ReactNode }) => ReactNode) | ImportString
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/vike-react-query/src/integration/FallbackErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | export { FallbackErrorBoundary }
2 |
3 | import { QueryErrorResetBoundary } from '@tanstack/react-query'
4 | import React, { CSSProperties, ReactElement } from 'react'
5 | import { ErrorBoundary, FallbackProps } from 'react-error-boundary'
6 |
7 | function FallbackErrorBoundary({ children }: { children: ReactElement }) {
8 | /* TODO: either remove this or properly check whether env is DEV:
9 | * - Safe check against process.env.NODE_ENV for server-side
10 | * - Safe check against import.meta.env.DEV for client-side
11 | */
12 | return (false as boolean) /*import.meta.env.DEV*/ ? (
13 |
14 | {({ reset }) => (
15 |
16 | {children}
17 |
18 | )}
19 |
20 | ) : (
21 | children
22 | )
23 | }
24 |
25 | function Fallback({ resetErrorBoundary, error }: FallbackProps) {
26 | return (
27 |
28 |
There was an error.
29 |
resetErrorBoundary()}>
30 | Try again
31 |
32 | {
33 | /* TODO: either remove this or properly check whether env is DEV:
34 | * - Safe check against process.env.NODE_ENV for server-side
35 | * - Safe check against import.meta.env.DEV for client-side
36 | */
37 | (false as boolean) /*import.meta.env.DEV*/ &&
{getErrorStack(error)}
38 | }
39 |
40 | )
41 | }
42 |
43 | function getErrorStack(error: unknown) {
44 | if (error && error instanceof Error) {
45 | return error.stack
46 | }
47 |
48 | return ''
49 | }
50 |
51 | const pageStyle: CSSProperties = {
52 | position: 'absolute',
53 | inset: 0,
54 | display: 'flex',
55 | flexDirection: 'column',
56 | alignItems: 'center',
57 | justifyContent: 'center',
58 | backgroundColor: '#ffe1e3',
59 | padding: 12,
60 | }
61 |
62 | const textStyle = {
63 | fontSize: 20,
64 | fontWeight: 500,
65 | color: '#f44250',
66 | }
67 |
68 | const buttonStyle: CSSProperties = {
69 | fontSize: 18,
70 | fontWeight: 500,
71 | marginTop: 16,
72 | outline: 0,
73 | border: 0,
74 | color: '#272727',
75 | boxShadow: '0px 1px 2px 0px #ffc5c5',
76 | backgroundColor: '#fff',
77 | padding: '8px 12px',
78 | borderRadius: 16,
79 | cursor: 'pointer',
80 | }
81 |
--------------------------------------------------------------------------------
/packages/vike-react-query/src/integration/StreamedHydration.tsx:
--------------------------------------------------------------------------------
1 | export { StreamedHydration }
2 |
3 | import type { QueryClient } from '@tanstack/react-query'
4 | import { dehydrate, hydrate, DehydratedState } from '@tanstack/react-query'
5 | import { uneval } from 'devalue'
6 | import type { ReactNode } from 'react'
7 | import { useStream } from 'react-streaming'
8 |
9 | declare global {
10 | interface Window {
11 | _rqd_?: { push: (entry: DehydratedState) => void } | DehydratedState[]
12 | _rqc_?: () => void
13 | }
14 | }
15 |
16 | /**
17 | * This component is responsible for:
18 | * - dehydrating the query client on the server
19 | * - hydrating the query client on the client
20 | * - if react-streaming is not used, it doesn't do anything
21 | */
22 | function StreamedHydration({ client, children }: { client: QueryClient; children: ReactNode }) {
23 | const stream = useStream()
24 |
25 | // stream is only avaiable in SSR
26 | const isSSR = !!stream
27 |
28 | if (isSSR) {
29 | stream.injectToStream(
30 | ``,
33 | )
34 | client.getQueryCache().subscribe((event) => {
35 | if (['added', 'updated'].includes(event.type) && event.query.state.status === 'success')
36 | stream.injectToStream(
37 | ``,
42 | )
43 | })
44 | }
45 |
46 | if (!isSSR && Array.isArray(window._rqd_)) {
47 | const onEntry = (entry: DehydratedState) => {
48 | hydrate(client, entry)
49 | }
50 | for (const entry of window._rqd_) {
51 | onEntry(entry)
52 | }
53 | window._rqd_ = { push: onEntry }
54 | }
55 | return children
56 | }
57 |
--------------------------------------------------------------------------------
/packages/vike-react-query/src/integration/Wrapper.tsx:
--------------------------------------------------------------------------------
1 | export { Wrapper }
2 |
3 | import { QueryClient, QueryClientProvider, type QueryClientConfig } from '@tanstack/react-query'
4 | import React, { ReactNode, useState } from 'react'
5 | import { StreamedHydration } from './StreamedHydration.js'
6 | import { usePageContext } from 'vike-react/usePageContext'
7 |
8 | function Wrapper({ children }: { children: ReactNode }) {
9 | const pageContext = usePageContext()
10 | const { queryClientConfig, FallbackErrorBoundary = PassThrough } = pageContext.config
11 | const [queryClient] = useState(() => {
12 | const config = typeof queryClientConfig === 'function' ? queryClientConfig(pageContext) : queryClientConfig
13 | return getQueryClient(config)
14 | })
15 |
16 | return (
17 |
18 |
19 | {children}
20 |
21 |
22 | )
23 | }
24 |
25 | function PassThrough({ children }: any) {
26 | return <>{children}>
27 | }
28 |
29 | let clientQueryClient: QueryClient | undefined
30 | function getQueryClient(config: QueryClientConfig | undefined) {
31 | if (!isBrowser()) return new QueryClient(config)
32 | // React may throw away a partially rendered tree if it suspends, and then start again from scratch.
33 | // If it's no suspense boundary between the creation of queryClient and useSuspenseQuery,
34 | // then the entire tree is thrown away, including the creation of queryClient, which may produce infinity refetchs
35 | // https://github.com/TanStack/query/issues/6116#issuecomment-1904051005
36 | // https://github.com/vikejs/vike-react/pull/157
37 | if (!clientQueryClient) clientQueryClient = new QueryClient(config)
38 | return clientQueryClient
39 | }
40 |
41 | function isBrowser() {
42 | // Using `typeof window !== 'undefined'` alone is not enough because some users use https://www.npmjs.com/package/ssr-window
43 | return typeof window !== 'undefined' && typeof window.scrollY === 'number'
44 | // Alternatively, test whether environment is a *real* browser: https://github.com/brillout/picocolors/blob/d59a33a0fd52a8a33e4158884069192a89ce0113/picocolors.js#L87-L89
45 | }
46 |
--------------------------------------------------------------------------------
/packages/vike-react-query/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | // Resolution
5 | "target": "ES2020",
6 | "module": "Node16",
7 | "moduleResolution": "Node16",
8 | // Libs
9 | "lib": ["ES2021", "DOM", "DOM.Iterable"],
10 | "types": ["vite/client"],
11 | // Strictness
12 | "strict": true,
13 | "noUncheckedIndexedAccess": true,
14 | "noImplicitAny": true,
15 | // Output
16 | "declaration": true,
17 | "noEmitOnError": false,
18 | "rootDir": "./src/",
19 | // Misc
20 | "esModuleInterop": true,
21 | "skipLibCheck": true,
22 | "jsx": "react"
23 | },
24 | "include": ["./src"]
25 | }
26 |
--------------------------------------------------------------------------------
/packages/vike-react-query/vitest.config.ts:
--------------------------------------------------------------------------------
1 | export { config as default }
2 |
3 | import { defineConfig } from 'vitest/config'
4 |
5 | const config = defineConfig({
6 | test: {
7 | // test/**/*.test.ts => @brillout/test-e2e
8 | include: ['**/*.spec.*'],
9 | environment: 'jsdom',
10 | },
11 | })
12 |
--------------------------------------------------------------------------------
/packages/vike-react-redux/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/packages/vike-react-redux/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.1.0 (2025-05-20)
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/packages/vike-react-redux/Wrapper.tsx:
--------------------------------------------------------------------------------
1 | export { Wrapper }
2 |
3 | import React from 'react'
4 | import { Provider } from 'react-redux'
5 | import { usePageContext } from 'vike-react/usePageContext'
6 | import type { Store } from '@reduxjs/toolkit'
7 |
8 | function Wrapper({ children }: { children: React.ReactNode }) {
9 | const pageContext = usePageContext()
10 | let store: undefined | Store
11 | if (pageContext.isClientSide) {
12 | store = pageContext.globalContext.store
13 | } else {
14 | store = pageContext.store
15 | }
16 | if (!store) return <>{children}>
17 | return {children}
18 | }
19 |
--------------------------------------------------------------------------------
/packages/vike-react-redux/config.ts:
--------------------------------------------------------------------------------
1 | export { config as default }
2 |
3 | import type { Config } from 'vike/types'
4 | import type { Store } from '@reduxjs/toolkit'
5 |
6 | const config = {
7 | name: 'vike-react-redux',
8 | require: {
9 | vike: '>=0.4.230',
10 | 'vike-react': '>=0.6.3',
11 | },
12 |
13 | passToClient: ['redux.ssrState'],
14 |
15 | meta: {
16 | redux: {
17 | env: { server: true, client: true },
18 | global: true,
19 | },
20 | },
21 |
22 | onCreatePageContext: 'import:vike-react-redux/__internal/onCreatePageContext:onCreatePageContext',
23 | onAfterRenderHtml: 'import:vike-react-redux/__internal/onAfterRenderHtml:onAfterRenderHtml',
24 | onBeforeRenderClient: 'import:vike-react-redux/__internal/onBeforeRenderClient:onBeforeRenderClient',
25 | Wrapper: 'import:vike-react-redux/__internal/Wrapper:Wrapper',
26 | } satisfies Config
27 |
28 | declare global {
29 | namespace Vike {
30 | interface Config {
31 | redux?: {
32 | createStore: (pageContext: PageContext | GlobalContextClient) => Store
33 | }
34 | }
35 | interface PageContext {
36 | // 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
37 | store: Store
38 | redux?: {
39 | ssrState?: Record
40 | }
41 | }
42 | interface GlobalContextClient {
43 | store: Store
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/vike-react-redux/onAfterRenderHtml.server.ts:
--------------------------------------------------------------------------------
1 | export { onAfterRenderHtml }
2 |
3 | import type { PageContextServer } from 'vike/types'
4 |
5 | function onAfterRenderHtml(pageContext: PageContextServer) {
6 | const configRedux = pageContext.config.redux
7 | if (!configRedux) return
8 | pageContext.redux ??= {}
9 | pageContext.redux.ssrState = pageContext.store.getState()
10 | }
11 |
--------------------------------------------------------------------------------
/packages/vike-react-redux/onBeforeRenderClient.client.ts:
--------------------------------------------------------------------------------
1 | export { onBeforeRenderClient }
2 |
3 | import type { PageContextClient } from 'vike/types'
4 |
5 | function onBeforeRenderClient(pageContext: PageContextClient) {
6 | const configRedux = pageContext.config.redux
7 | if (!configRedux) return
8 | pageContext.globalContext.store ??= configRedux.createStore(pageContext)
9 | }
10 |
--------------------------------------------------------------------------------
/packages/vike-react-redux/onCreatePageContext.server.ts:
--------------------------------------------------------------------------------
1 | export { onCreatePageContext }
2 |
3 | import type { PageContextServer } from 'vike/types'
4 |
5 | function onCreatePageContext(pageContext: PageContextServer) {
6 | const configRedux = pageContext.config.redux
7 | if (!configRedux) return
8 | pageContext.store = configRedux.createStore(pageContext)
9 | }
10 |
--------------------------------------------------------------------------------
/packages/vike-react-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vike-react-redux",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "exports": {
6 | "./config": "./dist/config.js",
7 | "./__internal/onCreatePageContext": "./dist/onCreatePageContext.server.js",
8 | "./__internal/onAfterRenderHtml": "./dist/onAfterRenderHtml.server.js",
9 | "./__internal/onBeforeRenderClient": "./dist/onBeforeRenderClient.client.js",
10 | "./__internal/Wrapper": "./dist/Wrapper.js"
11 | },
12 | "scripts": {
13 | "dev": "tsc --watch",
14 | "build": "rimraf dist/ && tsc",
15 | "release": "release-me patch",
16 | "release:minor": "release-me minor",
17 | "release:major": "release-me major",
18 | "release:commit": "release-me commit"
19 | },
20 | "peerDependencies": {
21 | "react-redux": ">=9",
22 | "react": ">=18",
23 | "vike": ">=0.4.230",
24 | "vike-react": ">=0.6.3"
25 | },
26 | "devDependencies": {
27 | "@brillout/release-me": "^0.4.2",
28 | "@reduxjs/toolkit": "^2.6.1",
29 | "@types/react": "^19.0.10",
30 | "react": "^19.0.0",
31 | "rimraf": "^5.0.5",
32 | "typescript": "^5.8.3",
33 | "vike": "^0.4.230",
34 | "vike-react": "0.6.4"
35 | },
36 | "typesVersions": {
37 | "*": {
38 | "config": [
39 | "dist/config.d.ts"
40 | ],
41 | "__internal/onAfterRenderHtml": [
42 | "dist/onAfterRenderHtml.d.ts"
43 | ],
44 | "__internal/onCreatePageContext": [
45 | "dist/onCreatePageContext.server.d.ts"
46 | ],
47 | "__internal/onBeforeRenderClient": [
48 | "dist/onBeforeRenderClient.d.ts"
49 | ],
50 | "__internal/Wrapper": [
51 | "dist/Wrapper.d.ts"
52 | ]
53 | }
54 | },
55 | "files": [
56 | "dist"
57 | ],
58 | "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-redux",
59 | "license": "MIT"
60 | }
61 |
--------------------------------------------------------------------------------
/packages/vike-react-redux/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "target": "ESNext",
5 | "module": "ESNext",
6 | "moduleResolution": "Bundler",
7 | "jsx": "react",
8 | "outDir": "./dist/",
9 | "skipLibCheck": true,
10 | "types": ["vike-react"],
11 | // Strictness
12 | "strict": true,
13 | "noUncheckedIndexedAccess": true,
14 | "noImplicitAny": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-components/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-components/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [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)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * simplify condition checks ([d67da36](https://github.com/vikejs/vike-react/commit/d67da3646e13f0b4c7493ff8c193e50c6f5ca15e))
7 | * stop overriding Wrapper meta ([ddcab7c](https://github.com/vikejs/vike-react/commit/ddcab7c21742b3701909b3698dca21b89c3dfebc))
8 |
9 |
10 |
11 | ## [1.0.1](https://github.com/vikejs/vike-react/compare/vike-react-styled-components@1.0.0...vike-react-styled-components@1.0.1) (2024-12-23)
12 |
13 |
14 | ### Bug Fixes
15 |
16 | * move `pageContext.styledComponentsSheet` to `pageContext.styledComponents.sheet` ([6fed80d](https://github.com/vikejs/vike-react/commit/6fed80dad3111f92d739a734c2a9e216747e1fa2))
17 |
18 |
19 |
20 | # 1.0.0 (2024-12-03)
21 |
22 |
23 | ### Features
24 |
25 | * new extension `vike-react-styled-components` ([#156](https://github.com/vikejs/vike-react/issues/156)) ([d28cd8e](https://github.com/vikejs/vike-react/commit/d28cd8e366a6aa0b5bc861de8c818b9ff5f5cf66))
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-components/Wrapper.server.tsx:
--------------------------------------------------------------------------------
1 | export { Wrapper }
2 |
3 | import React, { type ReactNode } from 'react'
4 | import { StyleSheetManager } from 'styled-components'
5 | import { usePageContext } from 'vike-react/usePageContext'
6 |
7 | function Wrapper({ children }: { children: ReactNode }) {
8 | const pageContext = usePageContext()
9 | const { styledComponents } = pageContext.config
10 |
11 | if (styledComponents === null) {
12 | return <>{children}>
13 | }
14 |
15 | return (
16 |
17 | {children}
18 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-components/config.ts:
--------------------------------------------------------------------------------
1 | export { config as default }
2 |
3 | import type { IStyleSheetManager, ServerStyleSheet } from 'styled-components'
4 | import type { Config } from 'vike/types'
5 |
6 | const config = {
7 | name: 'vike-react-styled-components',
8 | require: {
9 | vike: '>=0.4.211',
10 | 'vike-react': '>=0.4.13',
11 | },
12 | onAfterRenderHtml: 'import:vike-react-styled-components/__internal/onAfterRenderHtml:onAfterRenderHtml',
13 | onBeforeRenderHtml: 'import:vike-react-styled-components/__internal/onBeforeRenderHtml:onBeforeRenderHtml',
14 | Wrapper: 'import:vike-react-styled-components/__internal/Wrapper:Wrapper',
15 | meta: {
16 | styledComponents: {
17 | env: {
18 | server: true,
19 | client: false,
20 | },
21 | },
22 | },
23 | } satisfies Config
24 |
25 | declare global {
26 | namespace Vike {
27 | interface PageContext {
28 | styledComponents?: {
29 | sheet?: ServerStyleSheet
30 | }
31 | }
32 | interface Config {
33 | styledComponents?: null | {
34 | styleSheetManager?: Omit
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-components/onAfterRenderHtml.ts:
--------------------------------------------------------------------------------
1 | export { onAfterRenderHtml }
2 |
3 | import React from 'react'
4 | import { useConfig } from 'vike-react/useConfig'
5 | import type { PageContext } from 'vike/types'
6 |
7 | function onAfterRenderHtml(pageContext: PageContext) {
8 | const config = useConfig()
9 | const sheet = pageContext.styledComponents?.sheet
10 |
11 | if (sheet) {
12 | try {
13 | const styles = sheet.getStyleElement()
14 | config({
15 | Head: styles,
16 | })
17 | } catch (error) {
18 | throw error
19 | } finally {
20 | sheet.seal()
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-components/onBeforeRenderHtml.ts:
--------------------------------------------------------------------------------
1 | export { onBeforeRenderHtml }
2 |
3 | import { ServerStyleSheet } from 'styled-components'
4 | import type { PageContext } from 'vike/types'
5 |
6 | function onBeforeRenderHtml(pageContext: PageContext) {
7 | if (pageContext.config.styledComponents !== null) {
8 | pageContext.styledComponents = {
9 | sheet: new ServerStyleSheet(),
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vike-react-styled-components",
3 | "version": "1.0.2",
4 | "type": "module",
5 | "exports": {
6 | "./config": "./dist/config.js",
7 | "./__internal/onAfterRenderHtml": "./dist/onAfterRenderHtml.js",
8 | "./__internal/onBeforeRenderHtml": "./dist/onBeforeRenderHtml.js",
9 | "./__internal/Wrapper": "./dist/Wrapper.server.js"
10 | },
11 | "scripts": {
12 | "dev": "tsc --watch",
13 | "build": "rimraf dist/ && tsc",
14 | "release": "release-me patch",
15 | "release:minor": "release-me minor",
16 | "release:major": "release-me major",
17 | "release:commit": "release-me commit"
18 | },
19 | "peerDependencies": {
20 | "styled-components": ">=6",
21 | "react": ">=18",
22 | "vike-react": ">=0.4.13"
23 | },
24 | "devDependencies": {
25 | "@brillout/release-me": "^0.4.2",
26 | "@types/react": "^19.0.10",
27 | "react": "^19.0.0",
28 | "rimraf": "^5.0.5",
29 | "styled-components": "^6.1.13",
30 | "typescript": "^5.8.3",
31 | "vike": "^0.4.230",
32 | "vike-react": "0.6.4",
33 | "vite": "^6.2.5"
34 | },
35 | "typesVersions": {
36 | "*": {
37 | "config": [
38 | "dist/config.d.ts"
39 | ],
40 | "__internal/onAfterRenderHtml": [
41 | "dist/onAfterRenderHtml.d.ts"
42 | ],
43 | "__internal/onBeforeRenderHtml": [
44 | "dist/onBeforeRenderHtml.d.ts"
45 | ],
46 | "__internal/Wrapper": [
47 | "dist/Wrapper.server.d.ts"
48 | ]
49 | }
50 | },
51 | "files": [
52 | "dist"
53 | ],
54 | "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-styled-components",
55 | "license": "MIT"
56 | }
57 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-components/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "target": "ESNext",
5 | "module": "ESNext",
6 | "moduleResolution": "Bundler",
7 | "jsx": "react",
8 | "outDir": "./dist/",
9 | "skipLibCheck": true,
10 | "types": ["vike-react"],
11 | // Strictness
12 | "strict": true,
13 | "noUncheckedIndexedAccess": true,
14 | "noImplicitAny": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-jsx/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-jsx/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [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)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * simplify condition checks ([d67da36](https://github.com/vikejs/vike-react/commit/d67da3646e13f0b4c7493ff8c193e50c6f5ca15e))
7 | * stop overriding Wrapper meta ([ddcab7c](https://github.com/vikejs/vike-react/commit/ddcab7c21742b3701909b3698dca21b89c3dfebc))
8 |
9 |
10 |
11 | ## [1.0.1](https://github.com/vikejs/vike-react/compare/vike-react-styled-jsx@1.0.0...vike-react-styled-jsx@1.0.1) (2024-12-23)
12 |
13 |
14 | ### Bug Fixes
15 |
16 | * refactor multiple conditions into a single evaluation ([c4d5bb0](https://github.com/vikejs/vike-react/commit/c4d5bb02cecf068bc8ebe7499f1c4ebe852a6a58))
17 |
18 |
19 |
20 | # 1.0.0 (2024-12-17)
21 |
22 |
23 | ### Features
24 |
25 | * new extension `vike-react-styled-jsx` ([#158](https://github.com/vikejs/vike-react/issues/158)) ([2f3dab2](https://github.com/vikejs/vike-react/commit/2f3dab29096577da468fe01b046ef0b2b4a1f8c7))
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-jsx/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://www.npmjs.com/package/vike-react-styled-jsx)
4 |
5 | # `vike-react-styled-jsx`
6 |
7 | Integrates [styled-jsx](https://github.com/vercel/styled-jsx) into your [`vike-react`](https://vike.dev/vike-react) app.
8 |
9 | [Installation](#installation)
10 | [Settings](#settings)
11 | [Version history](https://github.com/vikejs/vike-react/blob/main/packages/vike-react-styled-jsx/CHANGELOG.md)
12 | [What it does](#what-it-does)
13 | [See also](#see-also)
14 |
15 |
16 |
17 | ## Installation
18 |
19 | 1. `npm install vike-react-styled-jsx styled-jsx`
20 | 2. Extend `+config.js`:
21 | ```js
22 | // pages/+config.js
23 |
24 | import vikeReact from "vike-react/config"
25 | import vikeReactStyledJsx from "vike-react-styled-jsx/config"
26 |
27 | export default {
28 | // ...
29 | extends: [vikeReact, vikeReactStyledJsx]
30 | }
31 | ```
32 |
33 | 3. Add `styled-jsx`'s Babel plugin:
34 | ```js
35 | // vite.config.js
36 | import react from "@vitejs/plugin-react"
37 | import vike from "vike/plugin"
38 |
39 | export default {
40 | plugins: [
41 | vike(),
42 | react({
43 | babel: {
44 | plugins: [["styled-jsx/babel"]]
45 | }
46 | })
47 | ]
48 | }
49 | ```
50 |
51 | 4. You can now use `styled-jsx` in any of your components.
52 | ```jsx
53 | function SomeComponent() {
54 | return (
55 |
56 |
Only this paragraph will get the style.
57 |
58 |
63 |
64 | )
65 | }
66 | ```
67 |
68 | > [!NOTE]
69 | > The `vike-react-styled-jsx` extension requires [`vike-react`](https://vike.dev/vike-react).
70 |
71 |
72 |
73 | ## Settings
74 |
75 | `vike-react-styled-jsx` provides a configuration `+styledJsx` to set the [CSP nonce for `styled-jsx`](https://github.com/vercel/styled-jsx#content-security-policy).
76 |
77 | > [!NOTE]
78 | > You need to set a ` ` tag with the same nonce.
79 |
80 | ```ts
81 | // pages/+styledJsx.js
82 | export { styledJsx }
83 |
84 | import nanoid from 'nanoid'
85 |
86 | const styledJsx = {
87 | nonce: Buffer.from(nanoid()).toString('base64') //ex: N2M0MDhkN2EtMmRkYi00MTExLWFhM2YtNDhkNTc4NGJhMjA3
88 | }
89 | ```
90 |
91 | You can remove the `vike-react-styled-jsx` integration from [some of your pages](https://vike.dev/config#inheritance):
92 |
93 | ```js
94 | // pages/about/+styledJsx.js
95 |
96 | export const styledJsx = null
97 | ```
98 |
99 | For full customization consider [ejecting](https://vike.dev/eject).
100 |
101 | > [!NOTE]
102 | > Consider making a [Pull Request before ejecting](https://vike.dev/eject#when-to-eject).
103 |
104 |
105 |
106 | ## What it does
107 |
108 | The `vike-react-styled-jsx` extension allows you to use `styled-jsx` without [FOUC](https://en.wikipedia.org/wiki/Flash_of_unstyled_content).
109 |
110 | It collects the page's styles during SSR and injects them in the HTML, ensuring that styles are applied early (before even JavaScript starts loading).
111 |
112 | You can learn more at:
113 | - [Vike > CSS-in-JS > Collect styles](https://vike.dev/css-in-js#collect-styles)
114 | - [styled-jsx README > Server Side Rendering](https://github.com/vercel/styled-jsx#server-side-rendering)
115 |
116 | For more details, have a look at the source code of `vike-react-styled-jsx` (which is small).
117 |
118 |
119 |
120 | ## See also
121 |
122 | - [Vike Docs > styled-jsx](https://vike.dev/styled-jsx)
123 | - [Vike Docs > CSS-in-JS](https://vike.dev/css-in-js)
124 | - [styled-jsx README](https://github.com/vercel/styled-jsx#readme)
125 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-jsx/Wrapper.server.tsx:
--------------------------------------------------------------------------------
1 | export { Wrapper }
2 |
3 | import React, { type ReactNode } from 'react'
4 | import { StyleRegistry } from 'styled-jsx'
5 | import { usePageContext } from 'vike-react/usePageContext'
6 |
7 | function Wrapper({ children }: { children: ReactNode }) {
8 | const pageContext = usePageContext()
9 | const { styledJsx } = pageContext.config
10 |
11 | if (styledJsx === null) {
12 | return <>{children}>
13 | }
14 |
15 | return {children}
16 | }
17 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-jsx/config.ts:
--------------------------------------------------------------------------------
1 | export { config as default }
2 |
3 | import type { Config } from 'vike/types'
4 | import type { StyledJsxStyleRegistry } from 'styled-jsx'
5 |
6 | const config = {
7 | name: 'vike-react-styled-jsx',
8 | require: {
9 | vike: '>=0.4.211',
10 | 'vike-react': '>=0.4.13',
11 | },
12 | onBeforeRenderHtml: 'import:vike-react-styled-jsx/__internal/onBeforeRenderHtml:onBeforeRenderHtml',
13 | onAfterRenderHtml: 'import:vike-react-styled-jsx/__internal/onAfterRenderHtml:onAfterRenderHtml',
14 | Wrapper: 'import:vike-react-styled-jsx/__internal/Wrapper:Wrapper',
15 | meta: {
16 | styledJsx: {
17 | env: { server: true },
18 | },
19 | },
20 | } satisfies Config
21 |
22 | declare global {
23 | namespace Vike {
24 | interface PageContext {
25 | styledJsx?: {
26 | registry?: StyledJsxStyleRegistry
27 | }
28 | }
29 | interface Config {
30 | styledJsx?: null | {
31 | nonce?: string
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-jsx/onAfterRenderHtml.ts:
--------------------------------------------------------------------------------
1 | export { onAfterRenderHtml }
2 |
3 | import { useConfig } from 'vike-react/useConfig'
4 | import type { PageContext } from 'vike/types'
5 |
6 | function onAfterRenderHtml(pageContext: PageContext) {
7 | const config = useConfig()
8 | const registry = pageContext.styledJsx?.registry
9 |
10 | if (registry) {
11 | const nonce = pageContext.config.styledJsx?.nonce
12 | const styles = registry.styles({ nonce })
13 |
14 | config({
15 | Head: styles,
16 | })
17 |
18 | registry.flush()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-jsx/onBeforeRenderHtml.ts:
--------------------------------------------------------------------------------
1 | export { onBeforeRenderHtml }
2 |
3 | import { createStyleRegistry } from 'styled-jsx'
4 | import type { PageContext } from 'vike/types'
5 |
6 | function onBeforeRenderHtml(pageContext: PageContext) {
7 | if (pageContext.config.styledJsx !== null) {
8 | pageContext.styledJsx = {
9 | registry: createStyleRegistry(),
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-jsx/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vike-react-styled-jsx",
3 | "version": "1.0.2",
4 | "type": "module",
5 | "exports": {
6 | "./config": "./dist/config.js",
7 | "./__internal/onAfterRenderHtml": "./dist/onAfterRenderHtml.js",
8 | "./__internal/onBeforeRenderHtml": "./dist/onBeforeRenderHtml.js",
9 | "./__internal/Wrapper": "./dist/Wrapper.server.js"
10 | },
11 | "scripts": {
12 | "dev": "tsc --watch",
13 | "build": "rimraf dist/ && tsc",
14 | "release": "release-me patch",
15 | "release:minor": "release-me minor",
16 | "release:major": "release-me major",
17 | "release:commit": "release-me commit"
18 | },
19 | "peerDependencies": {
20 | "styled-jsx": ">=5",
21 | "react": ">=18",
22 | "vike-react": ">=0.4.13"
23 | },
24 | "devDependencies": {
25 | "@brillout/release-me": "^0.4.2",
26 | "@types/react": "^19.0.10",
27 | "react": "^19.0.0",
28 | "rimraf": "^5.0.5",
29 | "styled-jsx": "^5.1.6",
30 | "typescript": "^5.8.3",
31 | "vike": "^0.4.230",
32 | "vike-react": "0.6.4",
33 | "vite": "^6.2.5"
34 | },
35 | "typesVersions": {
36 | "*": {
37 | "config": [
38 | "dist/config.d.ts"
39 | ],
40 | "__internal/onAfterRenderHtml": [
41 | "dist/onAfterRenderHtml.d.ts"
42 | ],
43 | "__internal/onBeforeRenderHtml": [
44 | "dist/onBeforeRenderHtml.d.ts"
45 | ],
46 | "__internal/Wrapper": [
47 | "dist/Wrapper.server.d.ts"
48 | ]
49 | }
50 | },
51 | "files": [
52 | "dist"
53 | ],
54 | "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-styled-jsx",
55 | "license": "MIT"
56 | }
57 |
--------------------------------------------------------------------------------
/packages/vike-react-styled-jsx/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "target": "ESNext",
5 | "module": "ESNext",
6 | "moduleResolution": "Bundler",
7 | "jsx": "react",
8 | "outDir": "./dist/",
9 | "skipLibCheck": true,
10 | "types": ["vike-react"],
11 | // Strictness
12 | "strict": true,
13 | "noUncheckedIndexedAccess": true,
14 | "noImplicitAny": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.1.0 (2025-05-20)
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vike-react-zustand",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "main": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "exports": {
8 | ".": "./dist/index.js",
9 | "./config": "./dist/integration/config.js"
10 | },
11 | "scripts": {
12 | "dev": "tsc --watch",
13 | "build": "rimraf dist/ && tsc",
14 | "release": "release-me patch",
15 | "release:minor": "release-me minor",
16 | "release:commit": "release-me commit"
17 | },
18 | "peerDependencies": {
19 | "react": ">=18.0.0",
20 | "react-dom": ">=18.0.0",
21 | "react-streaming": ">=0.3.42",
22 | "vike-react": ">=0.4.13",
23 | "zustand": ">=5.0.0"
24 | },
25 | "devDependencies": {
26 | "@brillout/release-me": "^0.3.4",
27 | "@types/babel__core": "^7.20.5",
28 | "@types/node": "^20.11.17",
29 | "@types/react": "^19.0.10",
30 | "@types/react-dom": "^19.0.4",
31 | "react": "^19.0.0",
32 | "react-dom": "^19.0.0",
33 | "rimraf": "^5.0.5",
34 | "typescript": "^5.8.3",
35 | "vike": "^0.4.223",
36 | "vike-react": "0.6.4",
37 | "react-streaming": "^0.4.2",
38 | "vite": "^6.2.5",
39 | "zustand": "^5.0.3"
40 | },
41 | "dependencies": {
42 | "@babel/core": "^7.24.0",
43 | "@babel/types": "^7.24.0",
44 | "@brillout/json-serializer": "^0.5.15"
45 | },
46 | "typesVersions": {
47 | "*": {
48 | "config": [
49 | "dist/integration/config.d.ts"
50 | ]
51 | }
52 | },
53 | "files": [
54 | "dist"
55 | ],
56 | "repository": "https://github.com/vikejs/vike-react/tree/main/packages/vike-react-zustand",
57 | "license": "MIT"
58 | }
59 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/src/context.ts:
--------------------------------------------------------------------------------
1 | export { setPageContext }
2 | export { getPageContext }
3 |
4 | import type { PageContext } from 'vike/types'
5 | import { getGlobalObject } from './utils/getGlobalObject.js'
6 |
7 | const globalObject = getGlobalObject('context.ts', {
8 | pageContextCurrent: null as PageContext | null,
9 | })
10 |
11 | function setPageContext(pageContext: PageContext | null) {
12 | globalObject.pageContextCurrent = pageContext
13 | }
14 |
15 | function getPageContext() {
16 | return globalObject.pageContextCurrent
17 | }
18 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/src/getOrCreateStore.ts:
--------------------------------------------------------------------------------
1 | export { getOrCreateStore }
2 | export type { CreateStoreReturn }
3 |
4 | import { parse } from '@brillout/json-serializer/parse'
5 | import { stringify } from '@brillout/json-serializer/stringify'
6 | import type { PageContext } from 'vike/types'
7 | import { create as createZustand, StateCreator } from 'zustand'
8 | import { setPageContext } from './context.js'
9 | import { assert } from './utils/assert.js'
10 | import { getGlobalObject } from './utils/getGlobalObject.js'
11 | import { sanitizeForSerialization } from './utils/sanitizeForSerialization.js'
12 | import { assignDeep } from './utils/assignDeep.js'
13 |
14 | // Client-side cache (not used in SSR)
15 | const clientCache = import.meta.env.SSR
16 | ? null
17 | : getGlobalObject('getOrCreateStore.ts', {
18 | initializers: {} as Record>,
19 | stores: {} as Record>,
20 | })
21 |
22 | function getOrCreateStore({
23 | key,
24 | initializerFn,
25 | pageContext,
26 | stream,
27 | }: {
28 | key: string
29 | initializerFn: StateCreator
30 | pageContext: PageContext
31 | stream: ReturnType
32 | }): CreateStoreReturn {
33 | try {
34 | setPageContext(pageContext)
35 | if (import.meta.env.SSR) {
36 | pageContext._vikeReactZustandStoresServer ??= {}
37 | let store = pageContext._vikeReactZustandStoresServer[key] as CreateStoreReturn
38 | if (store) return store
39 | store = createStore_(initializerFn)
40 | const serverState = store.getInitialState()
41 | const transferableState = sanitizeForSerialization(serverState)
42 | assert(stream)
43 | stream.injectToStream(
44 | ``,
45 | )
46 | pageContext._vikeReactZustandStoresServer[key] = store
47 | return store
48 | } else {
49 | assert(clientCache)
50 | const storeNeedsRecreate = clientCache.initializers[key] !== initializerFn
51 | if (storeNeedsRecreate) {
52 | const store = createStore_(initializerFn)
53 | clientCache.stores[key] = store
54 | clientCache.initializers[key] = initializerFn
55 | assignServerStateOptional({ key, store })
56 | return store
57 | } else {
58 | const store = clientCache.stores[key]
59 | assert(store)
60 | return store
61 | }
62 | }
63 | } finally {
64 | setPageContext(null)
65 | }
66 | }
67 |
68 | type CreateStoreReturn = ReturnType>
69 | function createStore_(initializer: StateCreator) {
70 | return createZustand()(initializer)
71 | }
72 |
73 | declare global {
74 | var _vikeReactZustandState: undefined | Record
75 | }
76 | function assignServerStateOptional({ key, store }: { key: string; store: CreateStoreReturn }) {
77 | if (globalThis._vikeReactZustandState && globalThis._vikeReactZustandState[key]) {
78 | const clientState = store.getInitialState()
79 | const serverState = parse(globalThis._vikeReactZustandState[key])
80 | assert(clientState && typeof clientState === 'object')
81 | assert(serverState && typeof serverState === 'object')
82 | assignDeep(clientState, serverState)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/src/integration/config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'vike/types'
2 | import { vikeReactZustand } from '../plugin/index.js'
3 |
4 | export default {
5 | name: 'vike-react-zustand',
6 | require: {
7 | 'vike-react': '>=0.4.13',
8 | },
9 | vite: {
10 | plugins: [vikeReactZustand()],
11 | },
12 | } satisfies Config
13 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/src/integration/types.d.ts:
--------------------------------------------------------------------------------
1 | export type {}
2 |
3 | // 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)
4 |
5 | declare global {
6 | namespace Vike {
7 | interface PageContext {
8 | _vikeReactZustandStoresServer: { [key: string]: import('../getOrCreateStore.ts').CreateStoreReturn }
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/src/plugin/index.ts:
--------------------------------------------------------------------------------
1 | export { vikeReactZustand }
2 |
3 | import type { Plugin } from 'vite'
4 | import { transformCode } from './babelTransformer.js'
5 |
6 | function vikeReactZustand(): Plugin[] {
7 | return [
8 | {
9 | name: 'vike-react-zustand:config',
10 | configEnvironment() {
11 | return {
12 | resolve: {
13 | noExternal: ['vike-react-zustand'],
14 | },
15 | }
16 | },
17 | },
18 | {
19 | name: 'vike-react-zustand:transform',
20 | enforce: 'post',
21 | transform(code, id) {
22 | if (id.includes('node_modules') || !/[jt]sx?$/.test(id)) {
23 | return
24 | }
25 | return transformCode(code, id)
26 | },
27 | },
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/src/types.ts:
--------------------------------------------------------------------------------
1 | export type { StoreVanillaAndHook, StoreVanilla, StoreHookOnly, Create }
2 |
3 | import type { StateCreator, StoreApi, StoreMutatorIdentifier } from 'zustand'
4 |
5 | /**
6 | * The store hook function that is returned by createWrapped
7 | */
8 | type StoreHookOnly = {
9 | (): T
10 | (selector: (state: T) => U): U
11 | }
12 |
13 | /**
14 | * Just the store API without the hook functionality
15 | */
16 | type StoreVanilla = StoreApi
17 |
18 | /**
19 | * Combined type used in the React context
20 | */
21 | type StoreVanillaAndHook = StoreVanilla & {
22 | (): any
23 | (selector: (state: any) => U): U
24 | }
25 |
26 | /**
27 | * The create function type with support for the key parameter
28 | */
29 | type Create = {
30 | // Direct call with initializer
31 | (initializer: StateCreator): StoreHookOnly
32 |
33 | // Direct call with key and initializer
34 | (
35 | key: string,
36 | initializer: StateCreator,
37 | ): StoreHookOnly
38 |
39 | // Curried call with no arguments
40 | (): (
41 | initializer: StateCreator,
42 | ) => StoreHookOnly
43 |
44 | // Curried call with key
45 | (
46 | key: string,
47 | ): (initializer: StateCreator) => StoreHookOnly
48 | }
49 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/src/utils/assert.ts:
--------------------------------------------------------------------------------
1 | export { assert, assertUsage }
2 |
3 | function assert(condition: unknown): asserts condition {
4 | if (condition) return
5 | throw new Error(
6 | "You stumbled upon a bug in vike-react-zustand's source code. Reach out on GitHub and we will fix the bug.",
7 | )
8 | }
9 |
10 | function assertUsage(condition: unknown, message: string): asserts condition {
11 | if (condition) return
12 | throw new Error('Wrong usage: ' + message)
13 | }
14 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/src/utils/assignDeep.ts:
--------------------------------------------------------------------------------
1 | // Credits: https://github.com/radashi-org/radashi/blob/main/src/object/assign.ts
2 |
3 | export { assignDeep }
4 |
5 | function assignDeep(initial: Record, override: Record) {
6 | if (!initial || !override) {
7 | return initial ?? override ?? {}
8 | }
9 | for (const key of Object.keys(override)) {
10 | initial[key] =
11 | isPlainObject(initial[key]) && isPlainObject(override[key])
12 | ? assignDeep(initial[key], override[key])
13 | : override[key]
14 | }
15 | return initial
16 | }
17 |
18 | function isPlainObject(value: any): value is object {
19 | if (typeof value !== 'object' || value === null) {
20 | return false
21 | }
22 |
23 | const prototype = Object.getPrototypeOf(value)
24 | return (
25 | // Fast path for most common objects.
26 | prototype === Object.prototype ||
27 | // Support objects created without a prototype.
28 | prototype === null ||
29 | // Support plain objects from other realms.
30 | Object.getPrototypeOf(prototype) === null
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/src/utils/getGlobalObject.ts:
--------------------------------------------------------------------------------
1 | export function getGlobalObject = never>(
2 | // We use the filename as key; each `getGlobalObject()` call should live in a unique filename.
3 | key: `${string}.ts`,
4 | defaultValue: T,
5 | ): T {
6 | const allGlobalObjects = (globalThis.__vike_react_zustand = globalThis.__vike_react_zustand || {})
7 | const globalObject = (allGlobalObjects[key] = (allGlobalObjects[key] as T) || defaultValue)
8 | return globalObject
9 | }
10 | declare global {
11 | var __vike_react_zustand: undefined | Record>
12 | }
13 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/src/utils/sanitizeForSerialization.ts:
--------------------------------------------------------------------------------
1 | export { sanitizeForSerialization }
2 |
3 | /**
4 | * Sanitizes data for serialization by removing functions, promises, and undefined values.
5 | * Creates a deep copy of the input with all non-serializable values removed.
6 | */
7 | function sanitizeForSerialization(input: T, visited = new WeakSet()): T | undefined {
8 | if (typeof input !== 'object' || input === null) {
9 | return input
10 | }
11 | if (visited.has(input)) {
12 | return input
13 | }
14 | visited.add(input)
15 | if (Array.isArray(input)) {
16 | const output = []
17 | for (const value of input) {
18 | if (include(value)) {
19 | const ret = sanitizeForSerialization(value, visited)
20 | if (include(ret)) {
21 | output.push(ret)
22 | } else {
23 | // Skip the whole array, we can't skip one in the middle of an ordered array
24 | return undefined
25 | }
26 | } else {
27 | return undefined
28 | }
29 | }
30 | return output as T
31 | }
32 | if (input instanceof Map) {
33 | const output = new Map()
34 | for (const [key, value] of input.entries()) {
35 | if (include(value)) {
36 | const ret = sanitizeForSerialization(value, visited)
37 | if (include(ret)) {
38 | output.set(key, ret)
39 | }
40 | }
41 | }
42 | return output as T
43 | }
44 | if (input instanceof Set) {
45 | const output = new Set()
46 | for (const value of input.values()) {
47 | if (include(value)) {
48 | const ret = sanitizeForSerialization(value, visited)
49 | if (include(ret)) {
50 | output.add(ret)
51 | }
52 | }
53 | }
54 | return output as T
55 | }
56 | const output: { [key: string]: any } = {}
57 | for (const key in input) {
58 | if (Object.prototype.hasOwnProperty.call(input, key)) {
59 | const value = input[key]
60 | if (include(value)) {
61 | const ret = sanitizeForSerialization(value, visited)
62 | if (include(ret)) {
63 | output[key] = ret
64 | }
65 | }
66 | }
67 | }
68 | return output as T
69 | }
70 | /**
71 | * Determines if a value should be included in the sanitized output.
72 | * Excludes functions, promises, undefined, and empty objects/collections.
73 | */
74 | function include(value: unknown): boolean {
75 | if (isPromiseLike(value) || typeof value === 'function' || value === undefined) {
76 | return false
77 | }
78 |
79 | if (value instanceof Map || value instanceof Set) {
80 | return value.size > 0
81 | }
82 |
83 | if (typeof value === 'object' && value !== null) {
84 | return Object.keys(value).length > 0
85 | }
86 |
87 | return true
88 | }
89 |
90 | /**
91 | * Checks if a value is promise-like (has then and catch methods).
92 | */
93 | function isPromiseLike(value: unknown): boolean {
94 | return Boolean(
95 | value &&
96 | typeof value === 'object' &&
97 | typeof (value as any).then === 'function' &&
98 | typeof (value as any).catch === 'function',
99 | )
100 | }
101 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/src/withPageContext.ts:
--------------------------------------------------------------------------------
1 | export { withPageContext }
2 |
3 | import type { PageContext } from 'vike/types'
4 | import type { StateCreator, StoreMutatorIdentifier } from 'zustand'
5 | import { getPageContext } from './context.js'
6 | import { assert } from './utils/assert.js'
7 |
8 | type WithPageContext = <
9 | T,
10 | Mps extends [StoreMutatorIdentifier, unknown][] = [],
11 | Mcs extends [StoreMutatorIdentifier, unknown][] = [],
12 | >(
13 | f: (pageContext: PageContext) => StateCreator,
14 | ) => StateCreator
15 |
16 | /**
17 | * Middleware to make `pageContext` available to the store during initialization.
18 | *
19 | * Example usage:
20 | *
21 | * ```ts
22 | * interface Store {
23 | * user: {
24 | * id: number
25 | * firstName: string
26 | * }
27 | * }
28 | *
29 | * const useStore = create()(
30 | * withPageContext((pageContext) => (set, get, store) => ({
31 | * user: pageContext.user
32 | * }))
33 | * )
34 | * ```
35 | *
36 | * https://github.com/vikejs/vike-react/tree/main/packages/vike-react-zustand
37 | */
38 | const withPageContext: WithPageContext = (fn) => (set, get, store) => {
39 | const pageContext = getPageContext()
40 | assert(pageContext)
41 | return fn(pageContext)(set, get, store)
42 | }
43 |
--------------------------------------------------------------------------------
/packages/vike-react-zustand/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | "rootDir": "./src/",
5 | // Resolution
6 | "target": "ES2020",
7 | "module": "Node16",
8 | "moduleResolution": "Node16",
9 | // Libs
10 | "lib": ["ES2021", "DOM", "DOM.Iterable"],
11 | "types": ["vite/client"],
12 | // Strictness
13 | "strict": true,
14 | "noUncheckedIndexedAccess": true,
15 | "noImplicitAny": true,
16 | // Output
17 | "declaration": true,
18 | "noEmitOnError": false,
19 | // Misc
20 | "esModuleInterop": true,
21 | "skipLibCheck": true,
22 | "jsx": "react"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/vike-react/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /dist/
3 |
--------------------------------------------------------------------------------
/packages/vike-react/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Building
4 |
5 | Build `vike-react`:
6 |
7 | ```bash
8 | git clone git@github.com:vikejs/vike-react
9 | cd vike-react/
10 | pnpm install
11 | pnpm build
12 | ```
13 |
14 | > Note that you'll need [pnpm](https://pnpm.io/) for development, which you can install with `$ npm install -g pnpm`.
15 | >
16 | > However pnpm isn't needed for simply making use of the npm package `vike-react`: any package manager will do.
17 |
18 | ## Validating
19 |
20 | ### Running the examples
21 |
22 | You can then test your modifications against an example, e.g. `examples/full/`:
23 |
24 | ```bash
25 | cd examples/full/
26 | pnpm dev
27 | cd ../../
28 | ```
29 |
30 | ## Releasing
31 |
32 | In order to release the next patch version (`MAJOR.MINOR.PATCH`, see [Semantic Versioning](https://semver.org/)), run:
33 |
34 | ```bash
35 | cd packages/vike-react/
36 | pnpm release
37 | cd ../../
38 | ```
39 |
40 | This will:
41 |
42 | - update the version number and dependencies in `package.json`,
43 | - extend [`CHANGELOG.md`](CHANGELOG.md),
44 | - update the `pnpm-lock.yaml` file,
45 | - create a `release: vike-react@1.2.3` git commit and push it,
46 | - create a `vike-react@1.2.3` git tag and push it,
47 | - build the package and publish it to npm.
48 |
49 | Extend [`CHANGELOG.md`](CHANGELOG.md) if anything is missing, as the release script:
50 |
51 | - only picks up commits
52 | [that match the pattern of "Feature", "Fix", "Performance Improvement" or "Breaking Changes"](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-cli),
53 | - mistakenly picks up commits touching all packages in this monorepo, not only `vike-react`.
54 |
--------------------------------------------------------------------------------
/packages/vike-react/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://www.npmjs.com/package/vike-react)
4 |
5 | See [Vike Docs > vike-react](https://vike.dev/vike-react).
6 |
--------------------------------------------------------------------------------
/packages/vike-react/eject.config.js:
--------------------------------------------------------------------------------
1 | // This is work-in-progress, see:
2 | // - https://github.com/brillout/playground_eject-vike-react
3 | // - https://github.com/snake-py/eject/issues/4#issuecomment-2506217514
4 | export const config = {
5 | files: 'src/*',
6 | operations: [
7 | 'mv src/* .',
8 | 'mv integration renderer',
9 | 'mv config.ts renderer/+config.ts',
10 | 'mv renderer/onRenderHtml.tsx renderer/+onRenderHtml.tsx',
11 | 'mv renderer/onRenderClient.tsx renderer/+onRenderClient.tsx',
12 | 'mv renderer/Loading.tsx renderer/+Loading.tsx',
13 | 'rm index.ts',
14 | ],
15 | }
16 |
--------------------------------------------------------------------------------
/packages/vike-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vike-react",
3 | "version": "0.6.4",
4 | "repository": "https://github.com/vikejs/vike-react",
5 | "type": "module",
6 | "exports": {
7 | "./usePageContext": "./dist/hooks/usePageContext.js",
8 | "./useData": "./dist/hooks/useData.js",
9 | "./useConfig": {
10 | "browser": "./dist/hooks/useConfig/useConfig-client.js",
11 | "default": "./dist/hooks/useConfig/useConfig-server.js"
12 | },
13 | "./Config": {
14 | "browser": "./dist/components/Config/Config-client.js",
15 | "default": "./dist/components/Config/Config-server.js"
16 | },
17 | "./Head": {
18 | "browser": "./dist/components/Head/Head-client.js",
19 | "default": "./dist/components/Head/Head-server.js"
20 | },
21 | "./clientOnly": "./dist/helpers/clientOnly.js",
22 | "./ClientOnly": "./dist/components/ClientOnly.js",
23 | ".": "./dist/index.js",
24 | "./config": "./dist/config.js",
25 | "./__internal/integration/onRenderHtml": "./dist/integration/onRenderHtml.js",
26 | "./__internal/integration/onRenderClient": "./dist/integration/onRenderClient.js",
27 | "./__internal/integration/Loading": "./dist/integration/Loading.js"
28 | },
29 | "dependencies": {
30 | "react-streaming": "^0.4.2"
31 | },
32 | "peerDependencies": {
33 | "react": ">=19",
34 | "react-dom": ">=19",
35 | "vike": ">=0.4.182"
36 | },
37 | "scripts": {
38 | "dev": "tsc --watch",
39 | "build": "rimraf dist/ && tsc && pnpm run build:css",
40 | "build:css": "cp src/integration/Loading.css dist/integration/Loading.css",
41 | "release": "release-me patch",
42 | "release:minor": "release-me minor",
43 | "release:commit": "release-me commit"
44 | },
45 | "devDependencies": {
46 | "@biomejs/biome": "^1.6.4",
47 | "@brillout/release-me": "^0.4.2",
48 | "@types/node": "^20.11.17",
49 | "@types/react": "^19.0.10",
50 | "@types/react-dom": "^19.0.4",
51 | "react": "^19.0.0",
52 | "react-dom": "^19.0.0",
53 | "rimraf": "^5.0.5",
54 | "typescript": "^5.8.3",
55 | "vike": "^0.4.230",
56 | "vite": "^6.2.5"
57 | },
58 | "typesVersions": {
59 | "*": {
60 | "useData": [
61 | "./dist/hooks/useData.d.ts"
62 | ],
63 | "usePageContext": [
64 | "./dist/hooks/usePageContext.d.ts"
65 | ],
66 | "useConfig": [
67 | "./dist/hooks/useConfig/useConfig-server.d.ts"
68 | ],
69 | "Config": [
70 | "./dist/components/Config/Config-server.d.ts"
71 | ],
72 | "Head": [
73 | "./dist/components/Head/Head-server.d.ts"
74 | ],
75 | "ClientOnly": [
76 | "./dist/components/ClientOnly.d.ts"
77 | ],
78 | "clientOnly": [
79 | "./dist/helpers/clientOnly.d.ts"
80 | ],
81 | ".": [
82 | "./dist/index.d.ts"
83 | ],
84 | "config": [
85 | "./dist/config.d.ts"
86 | ],
87 | "__/internal/integration/onRenderHtml": [
88 | "./dist/integration/onRenderHtml.d.ts"
89 | ],
90 | "__/internal/integration/onRenderClient": [
91 | "./dist/integration/onRenderClient.d.ts"
92 | ]
93 | }
94 | },
95 | "main": "./dist/index.js",
96 | "types": "./dist/index.d.ts",
97 | "files": [
98 | "dist/"
99 | ],
100 | "license": "MIT",
101 | "keywords": [
102 | "react"
103 | ]
104 | }
105 |
--------------------------------------------------------------------------------
/packages/vike-react/src/components/ClientOnly.tsx:
--------------------------------------------------------------------------------
1 | export { ClientOnly }
2 |
3 | import React, { lazy, useEffect, useState, startTransition } from 'react'
4 | import type { ComponentType, ReactNode } from 'react'
5 |
6 | function ClientOnly({
7 | load,
8 | children,
9 | fallback,
10 | deps = [],
11 | }: {
12 | load: () => Promise<{ default: React.ComponentType } | React.ComponentType>
13 | children: (Component: React.ComponentType) => ReactNode
14 | fallback: ReactNode
15 | deps?: Parameters[1]
16 | }) {
17 | // TODO/next-major: remove this file/export
18 | console.warn('[vike-react][warning] is deprecated: use clientOnly() instead https://vike.dev/clientOnly')
19 |
20 | const [Component, setComponent] = useState | null>(null)
21 |
22 | useEffect(() => {
23 | const loadComponent = () => {
24 | const Component = lazy(() =>
25 | load()
26 | .then((LoadedComponent) => {
27 | return {
28 | default: () => children('default' in LoadedComponent ? LoadedComponent.default : LoadedComponent),
29 | }
30 | })
31 | .catch((error) => {
32 | console.error('Component loading failed:', error)
33 | return { default: () => Error loading component.
}
34 | }),
35 | )
36 | setComponent(Component)
37 | }
38 |
39 | startTransition(() => {
40 | loadComponent()
41 | })
42 | }, deps)
43 |
44 | return Component ? : fallback
45 | }
46 |
--------------------------------------------------------------------------------
/packages/vike-react/src/components/Config/Config-client.ts:
--------------------------------------------------------------------------------
1 | export { Config }
2 |
3 | import { useConfig } from '../../hooks/useConfig/useConfig-client.js'
4 | import type { ConfigFromHook } from '../../types/Config.js'
5 |
6 | function Config(props: ConfigFromHook): null {
7 | const config = useConfig()
8 | config(props)
9 | return null
10 | }
11 |
--------------------------------------------------------------------------------
/packages/vike-react/src/components/Config/Config-server.ts:
--------------------------------------------------------------------------------
1 | export { Config }
2 |
3 | import { useConfig } from '../../hooks/useConfig/useConfig-server.js'
4 | import type { ConfigFromHook } from '../../types/Config.js'
5 |
6 | /**
7 | * Set configurations inside React components.
8 | *
9 | * https://vike.dev/useConfig
10 | */
11 | function Config(props: ConfigFromHook): null {
12 | const config = useConfig()
13 | config(props)
14 | return null
15 | }
16 |
--------------------------------------------------------------------------------
/packages/vike-react/src/components/Head/Head-client.ts:
--------------------------------------------------------------------------------
1 | // https://vike.dev/Head#only-html
2 | export function Head(): null {
3 | return null
4 | }
5 |
--------------------------------------------------------------------------------
/packages/vike-react/src/components/Head/Head-server.ts:
--------------------------------------------------------------------------------
1 | export { Head }
2 |
3 | import { useConfig } from '../../hooks/useConfig/useConfig-server.js'
4 | import type React from 'react'
5 |
6 | /**
7 | * Add arbitrary `` tags.
8 | *
9 | * (The children are teleported to ``.)
10 | *
11 | * https://vike.dev/Head
12 | */
13 | function Head({ children }: { children: React.ReactNode }): null {
14 | const config = useConfig()
15 | config({ Head: children })
16 | return null
17 | }
18 |
--------------------------------------------------------------------------------
/packages/vike-react/src/config.ts:
--------------------------------------------------------------------------------
1 | export { config as default }
2 |
3 | import type { Config } from 'vike/types'
4 | import { ssrEffect } from './integration/ssrEffect.js'
5 | import { isNotFalse } from './utils/isNotFalse.js'
6 |
7 | const config = {
8 | // @eject-remove start
9 | name: 'vike-react',
10 | require: {
11 | vike: '>=0.4.182',
12 | },
13 |
14 | Loading: 'import:vike-react/__internal/integration/Loading:default',
15 |
16 | // https://vike.dev/onRenderHtml
17 | onRenderHtml: 'import:vike-react/__internal/integration/onRenderHtml:onRenderHtml',
18 | // https://vike.dev/onRenderClient
19 | onRenderClient: 'import:vike-react/__internal/integration/onRenderClient:onRenderClient',
20 |
21 | // @eject-remove end
22 | passToClient: [
23 | '_configFromHook',
24 | // https://github.com/vikejs/vike-react/issues/25
25 | process.env.NODE_ENV !== 'production' && '$$typeof',
26 | ].filter(isNotFalse),
27 |
28 | // https://vike.dev/clientRouting
29 | clientRouting: true,
30 | hydrationCanBeAborted: true,
31 |
32 | // https://vike.dev/meta
33 | meta: {
34 | Head: {
35 | env: { server: true },
36 | cumulative: true,
37 | },
38 | Layout: {
39 | env: { server: true, client: true },
40 | cumulative: true,
41 | },
42 | title: {
43 | env: { server: true, client: true },
44 | },
45 | description: {
46 | env: { server: true },
47 | },
48 | image: {
49 | env: { server: true },
50 | },
51 | viewport: {
52 | env: { server: true },
53 | },
54 | favicon: {
55 | env: { server: true },
56 | global: true,
57 | },
58 | lang: {
59 | env: { server: true, client: true },
60 | },
61 | bodyHtmlBegin: {
62 | env: { server: true },
63 | cumulative: true,
64 | global: true,
65 | },
66 | bodyHtmlEnd: {
67 | env: { server: true },
68 | cumulative: true,
69 | global: true,
70 | },
71 | htmlAttributes: {
72 | env: { server: true },
73 | global: true,
74 | cumulative: true, // for Vike extensions
75 | },
76 | bodyAttributes: {
77 | env: { server: true },
78 | global: true,
79 | cumulative: true, // for Vike extensions
80 | },
81 | ssr: {
82 | env: { config: true },
83 | effect: ssrEffect,
84 | },
85 | stream: {
86 | env: { server: true },
87 | cumulative: true,
88 | },
89 | streamIsRequired: {
90 | env: { server: true },
91 | },
92 | onBeforeRenderHtml: {
93 | env: { server: true },
94 | cumulative: true,
95 | },
96 | onAfterRenderHtml: {
97 | env: { server: true },
98 | cumulative: true,
99 | },
100 | onBeforeRenderClient: {
101 | env: { client: true },
102 | cumulative: true,
103 | },
104 | onAfterRenderClient: {
105 | env: { client: true },
106 | cumulative: true,
107 | },
108 | Wrapper: {
109 | cumulative: true,
110 | env: { client: true, server: true },
111 | },
112 | // TODO/next-major: move to +react.js > strictMode ?
113 | reactStrictMode: {
114 | env: { client: true, server: true },
115 | },
116 | Loading: {
117 | env: { server: true, client: true },
118 | },
119 | react: {
120 | cumulative: true,
121 | env: {},
122 | },
123 | },
124 | } satisfies Config
125 | // @eject-remove start
126 |
127 | // This is required to make TypeScript load the global interfaces Vike.Config and Vike.PageContext so that they're always loaded: we can assume that the user always imports this file over `import vikeReact from 'vike-react/config'`
128 | import './types/Config.js'
129 | import './types/PageContext.js'
130 | // @eject-remove end
131 |
--------------------------------------------------------------------------------
/packages/vike-react/src/helpers/clientOnly.tsx:
--------------------------------------------------------------------------------
1 | export { clientOnly }
2 |
3 | import React, {
4 | Suspense,
5 | forwardRef,
6 | lazy,
7 | useEffect,
8 | useState,
9 | type ComponentProps,
10 | type ComponentType,
11 | type ReactNode,
12 | } from 'react'
13 |
14 | function clientOnly>(
15 | load: () => Promise<{ default: T } | T>,
16 | ): ComponentType & { fallback?: ReactNode }> {
17 | // Client side: always bundled by Vite, import.meta.env.SSR === false
18 | // Server side: may or may not be bundled by Vite, import.meta.env.SSR === true || import.meta.env === undefined
19 | //@ts-expect-error
20 | import.meta.env ??= { SSR: true }
21 | if (import.meta.env.SSR) {
22 | return (props) => <>{props.fallback}>
23 | } else {
24 | const Component = lazy(() =>
25 | load()
26 | .then((LoadedComponent) => ('default' in LoadedComponent ? LoadedComponent : { default: LoadedComponent }))
27 | .catch((error) => {
28 | console.error('Component loading failed:', error)
29 | return { default: (() => Error loading component.
) as any }
30 | }),
31 | )
32 |
33 | return forwardRef((props, ref) => {
34 | const [mounted, setMounted] = useState(false)
35 | useEffect(() => {
36 | setMounted(true)
37 | }, [])
38 | if (!mounted) {
39 | return <>{props.fallback}>
40 | }
41 | const { fallback, ...rest } = props
42 | return (
43 | {props.fallback}>}>
44 | {/* @ts-ignore */}
45 |
46 |
47 | )
48 | }) as ComponentType & { fallback?: ReactNode }>
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/vike-react/src/hooks/useConfig/configsCumulative.ts:
--------------------------------------------------------------------------------
1 | export const configsCumulative = ['Head', 'bodyAttributes', 'htmlAttributes'] as const
2 | export type ConfigsCumulative = (typeof configsCumulative)[number]
3 |
--------------------------------------------------------------------------------
/packages/vike-react/src/hooks/useConfig/useConfig-client.ts:
--------------------------------------------------------------------------------
1 | export { useConfig }
2 |
3 | import type { PageContext } from 'vike/types'
4 | import type { PageContextInternal } from '../../types/PageContext.js'
5 | import type { ConfigFromHook } from '../../types/Config.js'
6 | import { usePageContext } from '../usePageContext.js'
7 | import { getPageContext } from 'vike/getPageContext'
8 | import { applyHeadSettings } from '../../integration/applyHeadSettings.js'
9 |
10 | function useConfig(): (config: ConfigFromHook) => void {
11 | // Vike hook
12 | let pageContext = getPageContext() as PageContext & PageContextInternal
13 | if (pageContext) return (config: ConfigFromHook) => setPageContextConfigFromHook(config, pageContext)
14 |
15 | // Component
16 | pageContext = usePageContext()
17 | return (config: ConfigFromHook) => {
18 | if (!('_headAlreadySet' in pageContext)) {
19 | setPageContextConfigFromHook(config, pageContext)
20 | } else {
21 | applyHead(config)
22 | }
23 | }
24 | }
25 |
26 | function setPageContextConfigFromHook(config: ConfigFromHook, pageContext: PageContextInternal) {
27 | pageContext._configFromHook ??= {}
28 | Object.assign(pageContext._configFromHook, config)
29 | }
30 |
31 | function applyHead(config: ConfigFromHook) {
32 | const { title, lang } = config
33 | applyHeadSettings(title, lang)
34 | }
35 |
--------------------------------------------------------------------------------
/packages/vike-react/src/hooks/useConfig/useConfig-server.ts:
--------------------------------------------------------------------------------
1 | export { useConfig }
2 | import type { PageContext } from 'vike/types'
3 | import type { PageContextInternal } from '../../types/PageContext.js'
4 | import type { ConfigFromHook } from '../../types/Config.js'
5 | import { usePageContext } from '../usePageContext.js'
6 | import { getPageContext } from 'vike/getPageContext'
7 | import { useStreamOptional } from 'react-streaming'
8 | import { objectKeys } from '../../utils/objectKeys.js'
9 | import { includes } from '../../utils/includes.js'
10 | import { assert } from '../../utils/assert.js'
11 | import { configsCumulative } from './configsCumulative.js'
12 |
13 | /**
14 | * Set configurations inside components and Vike hooks.
15 | *
16 | * https://vike.dev/useConfig
17 | */
18 | function useConfig(): (config: ConfigFromHook) => void {
19 | // Vike hook
20 | let pageContext = getPageContext() as PageContext & PageContextInternal
21 | if (pageContext) return (config: ConfigFromHook) => setPageContextConfigFromHook(config, pageContext)
22 |
23 | // Component
24 | pageContext = usePageContext()
25 | const stream = useStreamOptional()
26 | return (config: ConfigFromHook) => {
27 | if (!pageContext._headAlreadySet) {
28 | setPageContextConfigFromHook(config, pageContext)
29 | } else {
30 | assert(stream)
31 | // already sent to the browser => send DOM-manipulating scripts during HTML streaming
32 | apply(config, stream)
33 | }
34 | }
35 | }
36 |
37 | const configsClientSide = ['title']
38 | function setPageContextConfigFromHook(config: ConfigFromHook, pageContext: PageContext & PageContextInternal) {
39 | pageContext._configFromHook ??= {}
40 | objectKeys(config).forEach((configName) => {
41 | // Skip HTML only configs which the client-side doesn't need, saving KBs sent to the client as well as avoiding serialization errors.
42 | if (pageContext.isClientSideNavigation && !configsClientSide.includes(configName)) return
43 |
44 | if (!includes(configsCumulative, configName)) {
45 | // Overridable config
46 | const configValue = config[configName]
47 | if (configValue === undefined) return
48 | pageContext._configFromHook![configName] = configValue as any
49 | } else {
50 | // Cumulative config
51 | const configValue = config[configName]
52 | if (!configValue) return
53 | pageContext._configFromHook![configName] ??= []
54 | pageContext._configFromHook![configName].push(configValue as any)
55 | }
56 | })
57 | }
58 |
59 | type Stream = NonNullable>
60 | function apply(config: ConfigFromHook, stream: Stream) {
61 | const { title } = config
62 | if (title) {
63 | const htmlSnippet = ``
64 | stream.injectToStream(htmlSnippet)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/packages/vike-react/src/hooks/useData.tsx:
--------------------------------------------------------------------------------
1 | export { useData }
2 |
3 | import { usePageContext } from './usePageContext.js'
4 |
5 | function useData(): Data {
6 | const data = usePageContext()?.data
7 | return data as any
8 | }
9 |
--------------------------------------------------------------------------------
/packages/vike-react/src/hooks/usePageContext.tsx:
--------------------------------------------------------------------------------
1 | export { usePageContext }
2 | export { PageContextProvider }
3 |
4 | import React, { useContext } from 'react'
5 | import { getGlobalObject } from '../utils/getGlobalObject.js'
6 | import type { PageContext } from 'vike/types'
7 |
8 | const globalObject = getGlobalObject('PageContextProvider.ts', {
9 | reactContext: React.createContext(undefined as never),
10 | })
11 |
12 | function PageContextProvider({ pageContext, children }: { pageContext: PageContext; children: React.ReactNode }) {
13 | const { reactContext } = globalObject
14 | return {children}
15 | }
16 |
17 | /**
18 | * Access `pageContext` from any React component.
19 | *
20 | * https://vike.dev/usePageContext
21 | */
22 | function usePageContext(): PageContext {
23 | const { reactContext } = globalObject
24 | const pageContext = useContext(reactContext)
25 | return pageContext
26 | }
27 |
--------------------------------------------------------------------------------
/packages/vike-react/src/index.ts:
--------------------------------------------------------------------------------
1 | // TODO/next-major: remove this file/export
2 | console.warn(
3 | "[vike-react][warning][deprecation] Replace `import vikeReact from 'vike-react'` with `import vikeReact from 'vike-react/config'` (typically in your /pages/+config.js)",
4 | )
5 | export { default } from './config.js'
6 |
--------------------------------------------------------------------------------
/packages/vike-react/src/integration/Loading.css:
--------------------------------------------------------------------------------
1 | /*
2 | This CSS is loaded for all vike-react users, even if they don't use the component because it's imported not directly but over depednency injection, see:
3 | https://github.com/vikejs/vike/discussions/2340
4 | */
5 |
6 | @keyframes vike-react-shine {
7 | to {
8 | background-position-x: -200%;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/vike-react/src/integration/Loading.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | component: LoadingComponent,
3 | }
4 |
5 | import React from 'react'
6 | /* We can't import it here: https://github.com/vikejs/vike/issues/2460
7 | * - We import it inside onRenderClient.js instead.
8 | * - We'll be able to do it if Vite + Rolldown always transpiles the server-side.
9 | import './Loading.css'
10 | */
11 |
12 | function LoadingComponent() {
13 | return (
14 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/packages/vike-react/src/integration/applyHeadSettings.tsx:
--------------------------------------------------------------------------------
1 | export { applyHeadSettings }
2 |
3 | type Value = string | null | undefined
4 |
5 | // - We skip if `undefined` as we shouldn't remove values set by the Head setting.
6 | // - Setting a default prevents the previous value to be leaked: upon client-side navigation, the value set by the previous page won't be removed if the next page doesn't override it.
7 | // - Most of the time, the user sets a default himself (i.e. a value defined at /pages/+config.js)
8 | // - If he doesn't have a default then he can use `null` to opt into Vike's defaults
9 |
10 | function applyHeadSettings(title: Value, lang: Value) {
11 | if (title !== undefined) document.title = title || ''
12 | if (lang !== undefined) document.documentElement.lang = lang || 'en'
13 | }
14 |
--------------------------------------------------------------------------------
/packages/vike-react/src/integration/getHeadSetting.tsx:
--------------------------------------------------------------------------------
1 | export { getHeadSetting }
2 |
3 | import { isCallable } from '../utils/isCallable.js'
4 | import type { PageContext } from 'vike/types'
5 | import type { PageContextInternal } from '../types/PageContext.js'
6 | import type { ConfigFromHookResolved } from '../types/Config.js'
7 | import { configsCumulative } from '../hooks/useConfig/configsCumulative.js'
8 | import { includes } from '../utils/includes.js'
9 |
10 | // We use `any` instead of doing proper validation in order to save KBs sent to the client-side.
11 |
12 | function getHeadSetting(
13 | configName: keyof ConfigFromHookResolved,
14 | pageContext: PageContext & PageContextInternal,
15 | ): undefined | T {
16 | // Set by useConfig()
17 | const valFromUseConfig = pageContext._configFromHook?.[configName]
18 | // Set by +configName.js
19 | const valFromConfig = pageContext.config[configName]
20 |
21 | const getCallable = (val: unknown) => (isCallable(val) ? val(pageContext) : val)
22 | if (!includes(configsCumulative, configName)) {
23 | if (valFromUseConfig !== undefined) return valFromUseConfig as any
24 | return getCallable(valFromConfig) as any
25 | } else {
26 | return [
27 | //
28 | ...((valFromConfig as any) ?? []).map(getCallable),
29 | ...((valFromUseConfig as any) ?? []),
30 | ] as any
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/vike-react/src/integration/getPageElement.tsx:
--------------------------------------------------------------------------------
1 | export { getPageElement }
2 |
3 | import React, { Suspense, useEffect } from 'react'
4 | import type { PageContext } from 'vike/types'
5 | import { PageContextProvider } from '../hooks/usePageContext.js'
6 |
7 | function getPageElement(pageContext: PageContext): { page: React.JSX.Element; renderPromise: Promise } {
8 | const {
9 | Page,
10 | config: { Loading },
11 | } = pageContext
12 | let page = Page ? : null
13 |
14 | // Wrapping
15 | const addSuspense = (el: React.JSX.Element | null) => {
16 | if (!Loading?.layout) return el
17 | return }>{page}
18 | }
19 | page = addSuspense(page)
20 | ;[
21 | // Inner wrapping
22 | ...(pageContext.config.Layout || []),
23 | // Outer wrapping
24 | ...(pageContext.config.Wrapper || []),
25 | ].forEach((Wrap) => {
26 | page = {page}
27 | page = addSuspense(page)
28 | })
29 |
30 | page = {page}
31 |
32 | let renderPromiseResolve!: () => void
33 | let renderPromise = new Promise((r) => (renderPromiseResolve = r))
34 | page = {page}
35 |
36 | if (pageContext.config.reactStrictMode !== false) {
37 | page = {page}
38 | }
39 |
40 | return { page, renderPromise }
41 | }
42 |
43 | function RenderPromiseProvider({
44 | children,
45 | renderPromiseResolve,
46 | }: { children: React.ReactNode; renderPromiseResolve: () => void }) {
47 | useEffect(renderPromiseResolve)
48 | return children
49 | }
50 |
--------------------------------------------------------------------------------
/packages/vike-react/src/integration/onRenderClient.tsx:
--------------------------------------------------------------------------------
1 | // https://vike.dev/onRenderClient
2 | export { onRenderClient }
3 |
4 | import ReactDOM from 'react-dom/client'
5 | import { getHeadSetting } from './getHeadSetting.js'
6 | import type { OnRenderClientAsync, PageContextClient } from 'vike/types'
7 | import { getPageElement } from './getPageElement.js'
8 | import type { PageContextInternal } from '../types/PageContext.js'
9 | import { callCumulativeHooks } from '../utils/callCumulativeHooks.js'
10 | import { applyHeadSettings } from './applyHeadSettings.js'
11 | import { resolveReactOptions } from './resolveReactOptions.js'
12 | import './Loading.css' // See comment inside Loading.tsx
13 |
14 | let root: ReactDOM.Root
15 | const onRenderClient: OnRenderClientAsync = async (
16 | pageContext: PageContextClient & PageContextInternal,
17 | ): ReturnType => {
18 | pageContext._headAlreadySet = pageContext.isHydration
19 |
20 | // Use case:
21 | // - Store hydration https://github.com/vikejs/vike-react/issues/110
22 | await callCumulativeHooks(pageContext.config.onBeforeRenderClient, pageContext)
23 |
24 | const { page, renderPromise } = getPageElement(pageContext)
25 | pageContext.page = page
26 |
27 | // TODO: implement this? So that, upon errors, onRenderClient() throws an error and Vike can render the error page. As of April 2024 it isn't released yet.
28 | // - https://react-dev-git-fork-rickhanlonii-rh-root-options-fbopensource.vercel.app/reference/react-dom/client/createRoot#show-a-dialog-for-uncaught-errors
29 | // - https://react-dev-git-fork-rickhanlonii-rh-root-options-fbopensource.vercel.app/reference/react-dom/client/hydrateRoot#show-a-dialog-for-uncaught-errors
30 | const onUncaughtError = (_error: any, _errorInfo: any) => {}
31 |
32 | const container = document.getElementById('root')!
33 | const { hydrateRootOptions, createRootOptions } = resolveReactOptions(pageContext)
34 | if (
35 | pageContext.isHydration &&
36 | // Whether the page was [Server-Side Rendered](https://vike.dev/ssr).
37 | container.innerHTML !== ''
38 | ) {
39 | // First render while using SSR, i.e. [hydration](https://vike.dev/hydration)
40 | root = ReactDOM.hydrateRoot(container, page, hydrateRootOptions)
41 | } else {
42 | if (!root) {
43 | // First render without SSR
44 | root = ReactDOM.createRoot(container, createRootOptions)
45 | }
46 | root.render(page)
47 | }
48 | pageContext.root = root
49 |
50 | await renderPromise
51 |
52 | if (!pageContext.isHydration) {
53 | pageContext._headAlreadySet = true
54 | applyHead(pageContext)
55 | }
56 |
57 | // Use cases:
58 | // - Custom user settings: https://vike.dev/head-tags#custom-settings
59 | // - Testing tools: https://github.com/vikejs/vike-react/issues/95
60 | await callCumulativeHooks(pageContext.config.onAfterRenderClient, pageContext)
61 | }
62 |
63 | function applyHead(pageContext: PageContextClient) {
64 | const title = getHeadSetting('title', pageContext)
65 | const lang = getHeadSetting('lang', pageContext)
66 | applyHeadSettings(title, lang)
67 | }
68 |
--------------------------------------------------------------------------------
/packages/vike-react/src/integration/resolveReactOptions.ts:
--------------------------------------------------------------------------------
1 | export { resolveReactOptions }
2 |
3 | import type { PageContext } from 'vike/types'
4 | import type { ReactOptions } from '../types/Config.js'
5 | import { isCallable } from '../utils/isCallable.js'
6 | import { objectEntries } from '../utils/objectEntries.js'
7 |
8 | function resolveReactOptions(pageContext: PageContext) {
9 | const optionsAcc: ReactOptions = {}
10 | ;(pageContext.config.react ?? []).forEach((valUnresolved) => {
11 | const optionList = isCallable(valUnresolved) ? valUnresolved(pageContext) : valUnresolved
12 | if (!optionList) return
13 | objectEntries(optionList).forEach(([fnName, options]) => {
14 | if (!options) return
15 | optionsAcc[fnName] ??= {}
16 | objectEntries(options).forEach(([key, val]) => {
17 | if (!isCallable(val)) {
18 | // @ts-ignore
19 | optionsAcc[fnName][key] ??= val
20 | } else {
21 | const valPrevious = optionsAcc[fnName]![key] as any as Function | undefined
22 | // @ts-ignore
23 | optionsAcc[fnName][key] = (...args: unknown[]) => {
24 | valPrevious?.(...args)
25 | val(...args)
26 | }
27 | }
28 | })
29 | })
30 | })
31 | return optionsAcc
32 | }
33 |
--------------------------------------------------------------------------------
/packages/vike-react/src/integration/ssrEffect.ts:
--------------------------------------------------------------------------------
1 | export { ssrEffect }
2 |
3 | import type { ConfigEffect } from 'vike/types'
4 |
5 | function ssrEffect({ configDefinedAt, configValue }: Parameters[0]): ReturnType {
6 | if (typeof configValue !== 'boolean') throw new Error(`${configDefinedAt} should be a boolean`)
7 | const env = {
8 | // Always load on the client-side.
9 | client: true,
10 | // When the SSR flag is false, we want to render the page only on the client-side.
11 | // We achieve this by loading `Page` only on the client-side: when onRenderHtml()
12 | // gets a `Page` value that is undefined it skip server-side rendering.
13 | server: configValue !== false,
14 | }
15 | return {
16 | meta: {
17 | Page: { env },
18 | /* We don't do this to enable wrapping with
19 | Wrapper: { env }, */
20 | Layout: { env },
21 | Loading: { env },
22 | },
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/vike-react/src/types/PageContext.ts:
--------------------------------------------------------------------------------
1 | import type React from 'react'
2 | import type ReactDOM from 'react-dom/client'
3 | import type { ConfigFromHookResolved } from './Config.js'
4 | import type { PageHtmlStream } from '../integration/onRenderHtml.js'
5 |
6 | // https://vike.dev/pageContext#typescript
7 | declare global {
8 | namespace Vike {
9 | interface PageContext {
10 | /** The root React component of the page */
11 | Page?: () => React.ReactNode
12 | /** The root React element of the page */
13 | page?: React.JSX.Element
14 | /** The React root DOM container */
15 | root?: ReactDOM.Root
16 |
17 | /** The +Page.jsx component rendered to the HTML string. */
18 | pageHtmlString?: string
19 | /** The +Pagejsx component rendered to an HTML stream. */
20 | pageHtmlStream?: PageHtmlStream
21 | }
22 | }
23 | }
24 |
25 | // Internal usage
26 | export type PageContextInternal = {
27 | _configFromHook?: ConfigFromHookResolved
28 | _headAlreadySet?: boolean
29 | }
30 |
--------------------------------------------------------------------------------
/packages/vike-react/src/utils/assert.ts:
--------------------------------------------------------------------------------
1 | export function assert(condition: unknown): asserts condition {
2 | if (condition) return
3 | throw new Error('You stumbled upon a vike-react bug, reach out on GitHub.')
4 | }
5 |
--------------------------------------------------------------------------------
/packages/vike-react/src/utils/callCumulativeHooks.ts:
--------------------------------------------------------------------------------
1 | export { callCumulativeHooks }
2 |
3 | import { providePageContext } from 'vike/getPageContext'
4 | import { isCallable } from './isCallable.js'
5 | import type { PageContext } from 'vike/types'
6 |
7 | async function callCumulativeHooks(
8 | values: undefined | T[],
9 | pageContext: PageContext,
10 | ): Promise<(undefined | null | Exclude)[]> {
11 | if (!values) return []
12 | const valuesPromises = values.map((val) => {
13 | if (isCallable(val)) {
14 | providePageContext(pageContext)
15 | // Hook
16 | return val(pageContext)
17 | } else {
18 | // Plain value
19 | return val
20 | }
21 | })
22 | const valuesResolved = await Promise.all(valuesPromises)
23 | return valuesResolved
24 | }
25 |
--------------------------------------------------------------------------------
/packages/vike-react/src/utils/getGlobalObject.ts:
--------------------------------------------------------------------------------
1 | export function getGlobalObject = never>(
2 | // We use the filename as key; each `getGlobalObject()` call should live inside a file with a unique filename.
3 | key: `${string}.ts`,
4 | defaultValue: T,
5 | ): T {
6 | // @ts-ignore
7 | const globalObjectsAll = (globalThis[projectKey] = globalThis[projectKey] || {})
8 | const globalObject = (globalObjectsAll[key] = globalObjectsAll[key] || defaultValue)
9 | return globalObject
10 | }
11 | const projectKey = '_vike_react'
12 |
--------------------------------------------------------------------------------
/packages/vike-react/src/utils/getTagAttributesString.ts:
--------------------------------------------------------------------------------
1 | export { getTagAttributesString }
2 | export type { TagAttributes }
3 |
4 | type TagAttributes = Record
5 |
6 | function getTagAttributesString(tagAttributes: TagAttributes): string {
7 | const tagAttributesString = Object.entries(tagAttributes)
8 | .filter(([_key, value]) => value !== false && value !== null && value !== undefined)
9 | .map(([key, value]) => `${ensureIsValidAttributeName(key)}=${JSON.stringify(String(value))}`)
10 | .join(' ')
11 | if (tagAttributesString.length === 0) return ''
12 | return ` ${tagAttributesString}`
13 | }
14 |
15 | function ensureIsValidAttributeName(str: string): string {
16 | if (/^[a-z][a-z0-9\-]*$/i.test(str) && !str.endsWith('-')) return str
17 | throw new Error(`Invalid HTML tag attribute name ${JSON.stringify(str)}`)
18 | }
19 |
--------------------------------------------------------------------------------
/packages/vike-react/src/utils/includes.ts:
--------------------------------------------------------------------------------
1 | // https://stackoverflow.com/questions/56565528/typescript-const-assertions-how-to-use-array-prototype-includes/74213179#74213179
2 | /** Same as Array.prototype.includes() but with type inference */
3 | export function includes(values: readonly T[], x: unknown): x is T {
4 | return values.includes(x as any)
5 | }
6 | /*
7 | export function includes(arr: Arr, el: unknown): el is Arr[number] {
8 | return arr.includes(el as any)
9 | }
10 | */
11 |
--------------------------------------------------------------------------------
/packages/vike-react/src/utils/isCallable.ts:
--------------------------------------------------------------------------------
1 | export function isCallable any>(thing: T | unknown): thing is T {
2 | return thing instanceof Function || typeof thing === 'function'
3 | }
4 |
--------------------------------------------------------------------------------
/packages/vike-react/src/utils/isNotFalse.ts:
--------------------------------------------------------------------------------
1 | export function isNotFalse(val: T | false): val is T {
2 | return val !== false
3 | }
4 |
--------------------------------------------------------------------------------
/packages/vike-react/src/utils/isNotNullish.ts:
--------------------------------------------------------------------------------
1 | export function isNullish(val: unknown): val is null | undefined {
2 | return val === null || val === undefined
3 | }
4 | // someArray.filter(isNotNullish)
5 | export function isNotNullish(p: T | null | undefined): p is T {
6 | return !isNullish(p)
7 | }
8 |
--------------------------------------------------------------------------------
/packages/vike-react/src/utils/isObject.ts:
--------------------------------------------------------------------------------
1 | export function isObject(value: unknown): value is Record {
2 | return typeof value === 'object' && value !== null
3 | }
4 |
--------------------------------------------------------------------------------
/packages/vike-react/src/utils/isReactElement.ts:
--------------------------------------------------------------------------------
1 | export { isReactElement }
2 |
3 | import React, { isValidElement } from 'react'
4 |
5 | function isReactElement(value: ReactElement | ReactComponent): value is ReactElement {
6 | return isValidElement(value) || Array.isArray(value)
7 | }
8 | type ReactElement = React.ReactNode
9 | type ReactComponent = () => ReactElement
10 |
--------------------------------------------------------------------------------
/packages/vike-react/src/utils/isType.ts:
--------------------------------------------------------------------------------
1 | export function isType(_: Type) {}
2 |
--------------------------------------------------------------------------------
/packages/vike-react/src/utils/objectEntries.ts:
--------------------------------------------------------------------------------
1 | // https://stackoverflow.com/questions/60141960/typescript-key-value-relation-preserving-object-entries-type/75337277#75337277
2 | /** Same as Object.entries() but with type inference */
3 | export function objectEntries(obj: T): [keyof T, T[keyof T]][] {
4 | return Object.entries(obj) as any
5 | }
6 |
--------------------------------------------------------------------------------
/packages/vike-react/src/utils/objectKeys.ts:
--------------------------------------------------------------------------------
1 | // https://stackoverflow.com/questions/52856496/typescript-object-keys-return-string
2 | // https://github.com/sindresorhus/ts-extras/blob/main/source/object-keys.ts
3 | /** Same as Object.keys() but with type inference */
4 | export function objectKeys(obj: T): (keyof T)[] {
5 | return Object.keys(obj) as any
6 | }
7 |
--------------------------------------------------------------------------------
/packages/vike-react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist/",
4 | // Resolution
5 | "target": "ES2020",
6 | "module": "Node16",
7 | "moduleResolution": "Node16",
8 | // Libs
9 | "lib": ["ES2021", "DOM", "DOM.Iterable"],
10 | "types": ["vite/client"],
11 | // Strictness
12 | "strict": true,
13 | "noUncheckedIndexedAccess": true,
14 | "noImplicitAny": true,
15 | // Output
16 | "declaration": true,
17 | "noEmitOnError": false,
18 | "rootDir": "./src",
19 | // Misc
20 | "esModuleInterop": true,
21 | "skipLibCheck": true,
22 | "jsx": "react"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'packages/*'
3 | - 'examples/*'
4 |
--------------------------------------------------------------------------------