-
-
Notifications
You must be signed in to change notification settings - Fork 5.3k
fix(nuxt): render client page directly when not hydrating #30061
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
WalkthroughThe changes in this pull request modify the Changes
Assessment against linked issues
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (2)
test/fixtures/basic/pages/client-only-page/index.client.vue (1)
8-8
: Consider error handling for async data fetching.The async data fetching could benefit from error handling to gracefully handle failures.
-const { data } = await useAsyncData(() => new Promise(resolve => setTimeout(() => resolve('async data'), 40))) +const { data, error } = await useAsyncData(() => new Promise((resolve, reject) => { + try { + setTimeout(() => resolve('async data'), 40) + } catch (err) { + reject(err) + } +}))test/basic.test.ts (1)
601-607
: Consider simplifying the async data placeholder check.The current implementation uses a complex waitForFunction with error throwing. Consider using a more straightforward approach.
-await normalInitialPage.waitForFunction(() => { - if (document.querySelector('#async-data-placeholder')) { - throw new Error('Should not contain async data placeholder due to blocking nav behaviour') - } else if (document.querySelector('#async-data')) { - return true - } -}) +await normalInitialPage.waitForSelector('#async-data') +const placeholder = await normalInitialPage.$('#async-data-placeholder') +expect(placeholder).toBeNull()
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (2)
test/basic.test.ts
(2 hunks)test/fixtures/basic/pages/client-only-page/index.client.vue
(2 hunks)
🔇 Additional comments (1)
test/fixtures/basic/pages/client-only-page/index.client.vue (1)
34-45
: LGTM! Well-structured template with proper conditional rendering.
The template follows Vue best practices with clear conditional rendering and state display.
…/nuxt/nuxt into fix/return_page_out_of_hydration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Outside diff range and nitpick comments (5)
test/nuxt/client.test.ts (5)
6-8
: Fix formatting issues in import statements.The imports are correctly added for the new test functionality, but there are some minor formatting inconsistencies.
Apply these formatting fixes:
-import { flushPromises, mount} from "@vue/test-utils" -import { Suspense } from 'vue' +import { flushPromises, mount } from '@vue/test-utils' +import { Suspense } from 'vue' import { createClientPage } from '../../packages/nuxt/dist/components/runtime/client-component'🧰 Tools
🪛 eslint
[error] 6-6: Expected indentation of 0 spaces but found 1.
(@stylistic/indent)
[error] 6-6: A space is required before '}'.
(@stylistic/object-curly-spacing)
[error] 6-6: Strings must use singlequote.
(@stylistic/quotes)
[error] 7-7: Expected indentation of 0 spaces but found 1.
(@stylistic/indent)
[error] 7-7: '/home/jailuser/git/node_modules/vue/dist/vue.runtime.esm-bundler.js' imported multiple times.
(import/no-duplicates)
[error] 8-8: Expected 1 empty line after import statement not followed by another import.
(import/newline-after-import)
34-35
: Fix test description and formatting.The test description could be more descriptive, and there are formatting issues in the test declaration.
-describe('client page', () => { - it('Should be suspensed when out of hydration',async () =>{ +describe('client page', () => { + it('should show fallback while component is suspended during hydration', async () => {🧰 Tools
🪛 eslint
[error] 35-35: A space is required after ','.
(@stylistic/comma-spacing)
[error] 35-35: Multiple spaces found before '('.
(@stylistic/no-multi-spaces)
[error] 35-35: Missing space after =>.
(@stylistic/arrow-spacing)
36-39
: Consider using a more structured approach for Promise control.The current Promise setup could be improved using a helper function for better reusability and clarity.
-let resolve -const promise = new Promise((_resolve) => { - resolve = _resolve -}) +function createControlledPromise() { + let resolve: (value?: unknown) => void + const promise = new Promise((_resolve) => { + resolve = _resolve + }) + return { promise, resolve: () => resolve() } +} + +const { promise, resolve } = createControlledPromise()
41-46
: Improve component definition clarity and type safety.The test component could be more explicit and type-safe.
-const comp = defineComponent({ - async setup() { - await promise - return () => h('div', {id: 'async'}, 'async resolved') - } -}) +const AsyncTestComponent = defineComponent({ + name: 'AsyncTestComponent', + async setup() { + await promise + return () => h('div', { id: 'async' }, 'async resolved') + }, +})🧰 Tools
🪛 eslint
[error] 42-42: Missing space before function parentheses.
(@stylistic/space-before-function-paren)
[error] 44-44: A space is required after '{'.
(@stylistic/object-curly-spacing)
[error] 44-44: A space is required before '}'.
(@stylistic/object-curly-spacing)
[error] 45-46: Missing trailing comma.
(@stylistic/comma-dangle)
48-57
: Improve test wrapper component structure.The wrapper component could be more explicit and better formatted.
-const wrapper = mount({ - setup() { - return () => h('div', {}, [ - h(Suspense, {}, { - default: () => h(createClientPage(() => Promise.resolve(comp)), {}), - fallback: () => h('div', {id: 'fallback'}, 'loading') - }) - ]) - } -}) +const TestWrapper = defineComponent({ + name: 'TestWrapper', + setup() { + return () => h('div', {}, [ + h(Suspense, {}, { + default: () => h(createClientPage(() => Promise.resolve(AsyncTestComponent))), + fallback: () => h('div', { id: 'fallback' }, 'loading'), + }), + ]) + }, +}) + +const wrapper = mount(TestWrapper)🧰 Tools
🪛 eslint
[error] 49-49: Missing space before function parentheses.
(@stylistic/space-before-function-paren)
[error] 53-53: A space is required after '{'.
(@stylistic/object-curly-spacing)
[error] 53-53: A space is required before '}'.
(@stylistic/object-curly-spacing)
[error] 53-54: Missing trailing comma.
(@stylistic/comma-dangle)
[error] 54-55: Missing trailing comma.
(@stylistic/comma-dangle)
[error] 56-57: Missing trailing comma.
(@stylistic/comma-dangle)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
test/nuxt/client.test.ts
(2 hunks)
🧰 Additional context used
🪛 eslint
test/nuxt/client.test.ts
[error] 6-6: Expected indentation of 0 spaces but found 1.
(@stylistic/indent)
[error] 6-6: A space is required before '}'.
(@stylistic/object-curly-spacing)
[error] 6-6: Strings must use singlequote.
(@stylistic/quotes)
[error] 7-7: Expected indentation of 0 spaces but found 1.
(@stylistic/indent)
[error] 7-7: '/home/jailuser/git/node_modules/vue/dist/vue.runtime.esm-bundler.js' imported multiple times.
(import/no-duplicates)
[error] 8-8: Expected 1 empty line after import statement not followed by another import.
(import/newline-after-import)
[error] 16-16: Trailing spaces not allowed.
(@stylistic/no-trailing-spaces)
[error] 33-34: More than 1 blank line not allowed.
(@stylistic/no-multiple-empty-lines)
[error] 35-35: A space is required after ','.
(@stylistic/comma-spacing)
[error] 35-35: Multiple spaces found before '('.
(@stylistic/no-multi-spaces)
[error] 35-35: Missing space after =>.
(@stylistic/arrow-spacing)
[error] 40-40: Trailing spaces not allowed.
(@stylistic/no-trailing-spaces)
[error] 42-42: Missing space before function parentheses.
(@stylistic/space-before-function-paren)
[error] 44-44: A space is required after '{'.
(@stylistic/object-curly-spacing)
[error] 44-44: A space is required before '}'.
(@stylistic/object-curly-spacing)
[error] 45-46: Missing trailing comma.
(@stylistic/comma-dangle)
[error] 47-47: Trailing spaces not allowed.
(@stylistic/no-trailing-spaces)
[error] 49-49: Missing space before function parentheses.
(@stylistic/space-before-function-paren)
[error] 53-53: A space is required after '{'.
(@stylistic/object-curly-spacing)
[error] 53-53: A space is required before '}'.
(@stylistic/object-curly-spacing)
[error] 53-54: Missing trailing comma.
(@stylistic/comma-dangle)
[error] 54-55: Missing trailing comma.
(@stylistic/comma-dangle)
[error] 56-57: Missing trailing comma.
(@stylistic/comma-dangle)
[error] 66-66: Newline required at end of file but not found.
(@stylistic/eol-last)
🔇 Additional comments (1)
test/nuxt/client.test.ts (1)
34-66
: Verify test coverage aligns with PR objectives.
The test effectively verifies the component suspension during hydration, which aligns with the PR's goal of fixing page transitions. However, consider adding additional test cases.
Consider adding these additional test cases:
- Test with multiple async operations
- Test error handling scenarios
- Test with actual navigation between client-only pages
🧰 Tools
🪛 eslint
[error] 35-35: A space is required after ','.
(@stylistic/comma-spacing)
[error] 35-35: Multiple spaces found before '('.
(@stylistic/no-multi-spaces)
[error] 35-35: Missing space after =>.
(@stylistic/arrow-spacing)
[error] 40-40: Trailing spaces not allowed.
(@stylistic/no-trailing-spaces)
[error] 42-42: Missing space before function parentheses.
(@stylistic/space-before-function-paren)
[error] 44-44: A space is required after '{'.
(@stylistic/object-curly-spacing)
[error] 44-44: A space is required before '}'.
(@stylistic/object-curly-spacing)
[error] 45-46: Missing trailing comma.
(@stylistic/comma-dangle)
[error] 47-47: Trailing spaces not allowed.
(@stylistic/no-trailing-spaces)
[error] 49-49: Missing space before function parentheses.
(@stylistic/space-before-function-paren)
[error] 53-53: A space is required after '{'.
(@stylistic/object-curly-spacing)
[error] 53-53: A space is required before '}'.
(@stylistic/object-curly-spacing)
[error] 53-54: Missing trailing comma.
(@stylistic/comma-dangle)
[error] 54-55: Missing trailing comma.
(@stylistic/comma-dangle)
[error] 56-57: Missing trailing comma.
(@stylistic/comma-dangle)
[error] 66-66: Newline required at end of file but not found.
(@stylistic/eol-last)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (2)
test/nuxt/client.test.ts (2)
34-34
: Consider a more descriptive test nameThe current test name "Should be suspensed when out of hydration" could be more descriptive. Consider something like "should show fallback content while async setup resolves and render component after resolution".
33-67
: Consider adding more test cases for comprehensive coverageThe current test effectively verifies the happy path, but consider adding tests for:
- Error handling when the promise rejects
- Component cleanup on unmount
- Multiple concurrent transitions
Here's a suggested test case for error handling:
it('Should handle rejected promises gracefully', async () => { let reject const promise = new Promise((_, _reject) => { reject = _reject }) const comp = defineComponent({ async setup () { await promise return () => h('div', { id: 'async' }, 'async resolved') }, }) const wrapper = mount({ setup () { return () => h('div', {}, [ h(Suspense, {}, { default: () => h(createClientPage(() => Promise.resolve(comp)), {}), fallback: () => h('div', { id: 'fallback' }, 'loading'), error: () => h('div', { id: 'error' }, 'error occurred') }), ]) }, }) await flushPromises() expect(wrapper.find('#fallback').exists()).toBe(true) reject!(new Error('Test error')) await flushPromises() expect(wrapper.find('#error').exists()).toBe(true) expect(wrapper.find('#fallback').exists()).toBe(false) expect(wrapper.find('#async').exists()).toBe(false) })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
test/nuxt/client.test.ts
(2 hunks)
🔇 Additional comments (1)
test/nuxt/client.test.ts (1)
3-5
: LGTM: Imports are well-organized and appropriate
The added imports for Suspense
, flushPromises
, and mount
are essential for testing async component behavior and page transitions.
Co-authored-by: Daniel Roe <daniel@roe.dev>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (1)
packages/nuxt/src/components/runtime/client-component.ts (1)
19-27
: Document this pattern for consistent implementationThis implementation introduces a significant pattern for handling client-side navigation states. Consider:
- Adding inline documentation explaining the rationale behind different rendering strategies
- Updating Nuxt.js docs to reflect this pattern for handling client-only pages
- Creating a guide for implementing similar functionality in other parts of the application
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
packages/nuxt/src/components/runtime/client-component.ts
(2 hunks)
🔇 Additional comments (3)
packages/nuxt/src/components/runtime/client-component.ts (3)
4-4
: LGTM: Import matches framework conventions
The added import follows Nuxt.js conventions and matches the previously suggested pattern.
21-25
: Consider potential styling implications of div wrapper
During server-side rendering or hydration, the page is wrapped in an additional div
. This might affect layouts or styling that expect direct access to the page component.
Let's check for potential styling dependencies:
✅ Verification successful
Let me gather more specific information about styling that could be affected by the div wrapper.
Let me try one more search with a corrected pattern to find page-specific styling:
The div wrapper's styling impact appears minimal
Based on the codebase analysis, there's limited usage of styles that would be affected by the additional div wrapper. The few instances of flex layouts found are explicitly defined inline or in component-specific styles, and there are no global page-level styles that would be disrupted by the wrapper. The current implementation appears safe from a styling perspective.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for style-related code that might be affected
ast-grep --pattern 'style={$_}'
rg -g '*.vue' -g '*.css' "client-only"
Length of output: 1852
Script:
#!/bin/bash
# Look for direct styling on page components and potential wrapper-sensitive styles
rg -g '*.vue' -g '*.css' '(^\s*>|first-child|last-child|nth-child|direct descendant|:first|:last)'
# Check for any styling specifically targeting the page component
rg -g '*.vue' -g '*.css' 'page\s*{|\.page\s*{|#page\s*{'
# Look for any margin/padding reset patterns that might be affected
rg -g '*.vue' -g '*.css' '(margin|padding):\s*0'
Length of output: 6100
Script:
#!/bin/bash
# Look for page-specific styling with fixed pattern
rg -g '*.vue' -g '*.css' '(\.page|#page|\[data-page\])'
# Check for styles that might be affected by wrapper div
rg -g '*.vue' -g '*.css' '(display:\s*flex|display:\s*grid|position:\s*absolute|position:\s*fixed)'
Length of output: 474
19-27
: Implementation aligns with PR objectives
The conditional rendering logic correctly addresses the issue by:
- Using hydration state to determine rendering strategy
- Directly rendering the page when not hydrating, preventing blank pages during navigation
Let's verify the impact on client-side navigation:
✅ Verification successful
Based on the code changes and verification results, I can now provide a final response:
Implementation correctly prevents blank pages during navigation
The conditional rendering approach using nuxtApp.isHydrating
ensures proper handling of client-side navigation by:
- Wrapping the page in
ClientOnly
during hydration to prevent flashing - Using direct rendering when not hydrating for optimal performance
The implementation has been tested through client-side navigation tests and shows no issues with blank pages or transitions. The hydration check is properly used to determine the rendering strategy.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for related navigation handling code and tests
rg -A 5 "useAsyncData.*client.*page"
rg -A 5 "navigation.*client.*only"
Length of output: 744
Script:
#!/bin/bash
# Let's search for client-side navigation and transition related code
rg -A 10 "client.*transition" packages/nuxt/
rg -A 10 "ClientOnly.*page" packages/nuxt/
# Search for tests related to client-side navigation
fd "test|spec" -e ts -e js | xargs rg -l "client.*navigation"
# Look for other usages of ClientOnly component
ast-grep --pattern 'h(ClientOnly, $_, $_)'
Length of output: 493
Script:
#!/bin/bash
# Let's check the test file that mentions client navigation
cat test/basic.test.ts
# Let's also check for any existing issues or discussions about client-side navigation
rg -A 5 "blank.*page.*navigation"
rg -A 5 "hydration.*navigation"
# Look for any related router or navigation handling code
ast-grep --pattern 'export function useRouter() {
$$$
}'
Length of output: 119052
Script:
#!/bin/bash
# Let's check for any issues with blank pages during navigation
rg -A 5 "blank.*during.*navigation"
rg -A 5 "transition.*blank"
# Look for any hydration-related code in client-component
ast-grep --pattern 'if ($isHydrating || $nuxtApp.isHydrating) {
$$$
}'
# Check for any related tests
rg -A 5 "test.*client.*navigation" test/
Length of output: 1211
🔗 Linked issue
fix #30032
📚 Description
hey 👋
this PR fix the issue of promises not being awaited when changing page by directly returning the page if nuxt is not hydrating
TODO
Summary by CodeRabbit
New Features
Bug Fixes
Tests