@pythoughts/vue-skills-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -0
- package/index.mjs +139 -0
- package/package.json +34 -0
- package/skills/create-adaptable-composable/SKILL.md +76 -0
- package/skills/vue-best-practices/SKILL.md +154 -0
- package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
- package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
- package/skills/vue-best-practices/references/component-async.md +97 -0
- package/skills/vue-best-practices/references/component-data-flow.md +350 -0
- package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
- package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
- package/skills/vue-best-practices/references/component-slots.md +216 -0
- package/skills/vue-best-practices/references/component-suspense.md +228 -0
- package/skills/vue-best-practices/references/component-teleport.md +108 -0
- package/skills/vue-best-practices/references/component-transition-group.md +128 -0
- package/skills/vue-best-practices/references/component-transition.md +125 -0
- package/skills/vue-best-practices/references/composables.md +290 -0
- package/skills/vue-best-practices/references/directives.md +162 -0
- package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
- package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
- package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
- package/skills/vue-best-practices/references/plugins.md +166 -0
- package/skills/vue-best-practices/references/reactivity.md +346 -0
- package/skills/vue-best-practices/references/render-functions.md +201 -0
- package/skills/vue-best-practices/references/sfc.md +310 -0
- package/skills/vue-best-practices/references/state-management.md +135 -0
- package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
- package/skills/vue-debug-guides/SKILL.md +205 -0
- package/skills/vue-debug-guides/reference/animation-key-for-rerender.md +160 -0
- package/skills/vue-debug-guides/reference/animation-transitiongroup-performance.md +241 -0
- package/skills/vue-debug-guides/reference/async-component-error-handling.md +115 -0
- package/skills/vue-debug-guides/reference/async-component-keepalive-ref-issue.md +112 -0
- package/skills/vue-debug-guides/reference/async-component-suspense-control.md +84 -0
- package/skills/vue-debug-guides/reference/async-component-vue-router.md +109 -0
- package/skills/vue-debug-guides/reference/attrs-event-listener-merging.md +205 -0
- package/skills/vue-debug-guides/reference/checkbox-true-false-value-form-submission.md +118 -0
- package/skills/vue-debug-guides/reference/cleanup-side-effects.md +172 -0
- package/skills/vue-debug-guides/reference/click-events-on-components.md +180 -0
- package/skills/vue-debug-guides/reference/component-naming-conflicts.md +159 -0
- package/skills/vue-debug-guides/reference/component-ref-requires-defineexpose.md +176 -0
- package/skills/vue-debug-guides/reference/composable-avoid-hidden-side-effects.md +208 -0
- package/skills/vue-debug-guides/reference/composable-call-location-restrictions.md +141 -0
- package/skills/vue-debug-guides/reference/composable-naming-return-pattern.md +139 -0
- package/skills/vue-debug-guides/reference/composable-tovalue-inside-watcheffect.md +182 -0
- package/skills/vue-debug-guides/reference/composition-api-not-functional-programming.md +120 -0
- package/skills/vue-debug-guides/reference/composition-api-script-setup-async-context.md +203 -0
- package/skills/vue-debug-guides/reference/composition-api-vs-react-hooks-differences.md +156 -0
- package/skills/vue-debug-guides/reference/computed-array-mutation.md +148 -0
- package/skills/vue-debug-guides/reference/computed-conditional-dependencies.md +147 -0
- package/skills/vue-debug-guides/reference/computed-no-parameters.md +159 -0
- package/skills/vue-debug-guides/reference/computed-no-side-effects.md +107 -0
- package/skills/vue-debug-guides/reference/computed-return-value-readonly.md +160 -0
- package/skills/vue-debug-guides/reference/configure-app-before-mount.md +89 -0
- package/skills/vue-debug-guides/reference/declare-emits-for-documentation.md +212 -0
- package/skills/vue-debug-guides/reference/define-expose-before-await.md +192 -0
- package/skills/vue-debug-guides/reference/define-model-default-value-sync.md +139 -0
- package/skills/vue-debug-guides/reference/defineEmits-must-be-top-level.md +164 -0
- package/skills/vue-debug-guides/reference/defineEmits-no-runtime-and-type-mixed.md +170 -0
- package/skills/vue-debug-guides/reference/definemodel-object-mutation-no-emit.md +148 -0
- package/skills/vue-debug-guides/reference/dom-update-timing-nexttick.md +90 -0
- package/skills/vue-debug-guides/reference/dynamic-argument-constraints.md +146 -0
- package/skills/vue-debug-guides/reference/dynamic-component-registration-vite.md +147 -0
- package/skills/vue-debug-guides/reference/event-modifier-order-matters.md +101 -0
- package/skills/vue-debug-guides/reference/exact-modifier-for-precise-shortcuts.md +155 -0
- package/skills/vue-debug-guides/reference/fallthrough-attrs-overwrite-vue3.md +159 -0
- package/skills/vue-debug-guides/reference/in-dom-template-parsing-caveats.md +149 -0
- package/skills/vue-debug-guides/reference/inheritattrs-false-for-wrapper-components.md +230 -0
- package/skills/vue-debug-guides/reference/keepalive-router-nested-double-mount.md +222 -0
- package/skills/vue-debug-guides/reference/keepalive-transition-memory-leak.md +144 -0
- package/skills/vue-debug-guides/reference/keyup-modifier-timing.md +137 -0
- package/skills/vue-debug-guides/reference/lifecycle-dom-access-timing.md +216 -0
- package/skills/vue-debug-guides/reference/lifecycle-hooks-synchronous-registration.md +156 -0
- package/skills/vue-debug-guides/reference/lifecycle-ssr-awareness.md +184 -0
- package/skills/vue-debug-guides/reference/local-components-not-in-descendants.md +151 -0
- package/skills/vue-debug-guides/reference/mount-return-value.md +88 -0
- package/skills/vue-debug-guides/reference/multi-root-component-class-attrs.md +93 -0
- package/skills/vue-debug-guides/reference/native-event-collision-with-emits.md +162 -0
- package/skills/vue-debug-guides/reference/no-passive-with-prevent.md +141 -0
- package/skills/vue-debug-guides/reference/no-v-if-with-v-for.md +136 -0
- package/skills/vue-debug-guides/reference/perf-computed-object-stability.md +157 -0
- package/skills/vue-debug-guides/reference/perf-props-stability-update-optimization.md +140 -0
- package/skills/vue-debug-guides/reference/plugin-global-properties-sparingly.md +109 -0
- package/skills/vue-debug-guides/reference/plugin-install-before-mount.md +124 -0
- package/skills/vue-debug-guides/reference/plugin-prefer-provide-inject-over-global-properties.md +120 -0
- package/skills/vue-debug-guides/reference/plugin-typescript-type-augmentation.md +157 -0
- package/skills/vue-debug-guides/reference/prop-defineprops-scope-limitation.md +161 -0
- package/skills/vue-debug-guides/reference/provide-inject-debugging-challenges.md +203 -0
- package/skills/vue-debug-guides/reference/provide-inject-default-value-factory.md +244 -0
- package/skills/vue-debug-guides/reference/provide-inject-reactivity-not-automatic.md +226 -0
- package/skills/vue-debug-guides/reference/provide-inject-synchronous-setup.md +235 -0
- package/skills/vue-debug-guides/reference/reactive-destructuring.md +89 -0
- package/skills/vue-debug-guides/reference/reactivity-debugging-hooks.md +132 -0
- package/skills/vue-debug-guides/reference/reactivity-markraw-for-non-reactive.md +149 -0
- package/skills/vue-debug-guides/reference/reactivity-proxy-identity-hazard.md +96 -0
- package/skills/vue-debug-guides/reference/reactivity-same-tick-batching.md +166 -0
- package/skills/vue-debug-guides/reference/ref-value-access.md +61 -0
- package/skills/vue-debug-guides/reference/refs-in-collections-need-value.md +81 -0
- package/skills/vue-debug-guides/reference/render-function-avoid-internal-vnode-properties.md +151 -0
- package/skills/vue-debug-guides/reference/render-function-vnodes-must-be-unique.md +133 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-h-import-vue3.md +148 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-return-from-setup.md +148 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-slots-as-functions.md +168 -0
- package/skills/vue-debug-guides/reference/rendering-resolve-component-for-string-names.md +231 -0
- package/skills/vue-debug-guides/reference/select-initial-value-ios-bug.md +91 -0
- package/skills/vue-debug-guides/reference/self-referencing-component-name.md +157 -0
- package/skills/vue-debug-guides/reference/sfc-named-exports-forbidden.md +184 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-child-component-styling.md +156 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-dynamic-content.md +193 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-slot-content.md +242 -0
- package/skills/vue-debug-guides/reference/sfc-script-setup-reactivity.md +195 -0
- package/skills/vue-debug-guides/reference/slot-forwarding-to-child-components.md +143 -0
- package/skills/vue-debug-guides/reference/slot-implicit-default-content.md +155 -0
- package/skills/vue-debug-guides/reference/slot-name-reserved-prop.md +109 -0
- package/skills/vue-debug-guides/reference/slot-named-scoped-explicit-default.md +95 -0
- package/skills/vue-debug-guides/reference/slot-render-scope-parent-only.md +135 -0
- package/skills/vue-debug-guides/reference/slot-v-slot-on-components-or-templates-only.md +122 -0
- package/skills/vue-debug-guides/reference/ssr-hydration-mismatch-causes.md +280 -0
- package/skills/vue-debug-guides/reference/ssr-platform-specific-apis.md +256 -0
- package/skills/vue-debug-guides/reference/state-ssr-cross-request-pollution.md +276 -0
- package/skills/vue-debug-guides/reference/suspense-no-builtin-error-handling.md +127 -0
- package/skills/vue-debug-guides/reference/suspense-ssr-hydration-issues.md +159 -0
- package/skills/vue-debug-guides/reference/tailwind-dynamic-class-generation.md +144 -0
- package/skills/vue-debug-guides/reference/teleport-scoped-styles-limitation.md +191 -0
- package/skills/vue-debug-guides/reference/teleport-ssr-hydration.md +152 -0
- package/skills/vue-debug-guides/reference/teleport-target-must-exist.md +113 -0
- package/skills/vue-debug-guides/reference/template-expressions-restrictions.md +114 -0
- package/skills/vue-debug-guides/reference/template-functions-no-side-effects.md +187 -0
- package/skills/vue-debug-guides/reference/template-ref-null-with-v-if.md +123 -0
- package/skills/vue-debug-guides/reference/template-ref-unwrapping-top-level.md +104 -0
- package/skills/vue-debug-guides/reference/template-ref-v-for-order.md +172 -0
- package/skills/vue-debug-guides/reference/textarea-no-interpolation.md +72 -0
- package/skills/vue-debug-guides/reference/transition-group-flip-inline-elements.md +152 -0
- package/skills/vue-debug-guides/reference/transition-group-move-animation-position-absolute.md +130 -0
- package/skills/vue-debug-guides/reference/transition-group-no-default-wrapper-vue3.md +152 -0
- package/skills/vue-debug-guides/reference/transition-js-hooks-done-callback.md +251 -0
- package/skills/vue-debug-guides/reference/transition-nested-duration.md +182 -0
- package/skills/vue-debug-guides/reference/transition-reusable-scoped-style.md +245 -0
- package/skills/vue-debug-guides/reference/transition-router-view-appear.md +193 -0
- package/skills/vue-debug-guides/reference/transition-type-when-mixed.md +172 -0
- package/skills/vue-debug-guides/reference/transition-unmount-hook-timing.md +149 -0
- package/skills/vue-debug-guides/reference/ts-defineprops-boolean-default-false.md +225 -0
- package/skills/vue-debug-guides/reference/ts-defineprops-imported-types-limitations.md +281 -0
- package/skills/vue-debug-guides/reference/ts-event-handler-explicit-typing.md +213 -0
- package/skills/vue-debug-guides/reference/ts-reactive-no-generic-argument.md +196 -0
- package/skills/vue-debug-guides/reference/ts-shallowref-for-dynamic-components.md +218 -0
- package/skills/vue-debug-guides/reference/ts-template-ref-null-handling.md +249 -0
- package/skills/vue-debug-guides/reference/ts-template-type-casting.md +214 -0
- package/skills/vue-debug-guides/reference/ts-withdefaults-mutable-factory-function.md +171 -0
- package/skills/vue-debug-guides/reference/undeclared-emits-double-firing.md +195 -0
- package/skills/vue-debug-guides/reference/use-template-ref-vue35.md +158 -0
- package/skills/vue-debug-guides/reference/v-else-must-follow-v-if.md +136 -0
- package/skills/vue-debug-guides/reference/v-for-component-props.md +95 -0
- package/skills/vue-debug-guides/reference/v-for-computed-reverse-sort.md +86 -0
- package/skills/vue-debug-guides/reference/v-for-key-attribute.md +90 -0
- package/skills/vue-debug-guides/reference/v-for-range-starts-at-one.md +66 -0
- package/skills/vue-debug-guides/reference/v-if-null-check-order.md +171 -0
- package/skills/vue-debug-guides/reference/v-model-ignores-html-attributes.md +83 -0
- package/skills/vue-debug-guides/reference/v-model-ime-composition.md +83 -0
- package/skills/vue-debug-guides/reference/v-model-number-modifier-behavior.md +124 -0
- package/skills/vue-debug-guides/reference/v-show-template-limitation.md +124 -0
- package/skills/vue-debug-guides/reference/watch-async-cleanup.md +180 -0
- package/skills/vue-debug-guides/reference/watch-async-creation-memory-leak.md +176 -0
- package/skills/vue-debug-guides/reference/watch-deep-same-object-reference.md +165 -0
- package/skills/vue-debug-guides/reference/watch-flush-timing.md +189 -0
- package/skills/vue-debug-guides/reference/watch-reactive-property-getter.md +108 -0
- package/skills/vue-debug-guides/reference/watcheffect-async-dependency-tracking.md +173 -0
- package/skills/vue-debug-guides/reference/watcheffect-flush-post-for-refs.md +176 -0
- package/skills/vue-jsx-best-practices/SKILL.md +12 -0
- package/skills/vue-jsx-best-practices/reference/render-function-jsx-vue-vs-react.md +141 -0
- package/skills/vue-options-api-best-practices/SKILL.md +23 -0
- package/skills/vue-options-api-best-practices/reference/no-arrow-functions-in-lifecycle-hooks.md +95 -0
- package/skills/vue-options-api-best-practices/reference/no-arrow-functions-in-methods.md +68 -0
- package/skills/vue-options-api-best-practices/reference/stateful-methods-lifecycle.md +61 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-arrow-functions-validators.md +141 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-computed-return-types.md +192 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-proptype-complex-types.md +212 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-provide-inject-limitations.md +135 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-type-event-handlers.md +202 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-use-definecomponent.md +172 -0
- package/skills/vue-options-api-best-practices/reference/ts-strict-mode-options-api.md +197 -0
- package/skills/vue-pinia-best-practices/SKILL.md +21 -0
- package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
- package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
- package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
- package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
- package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
- package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
- package/skills/vue-router-best-practices/SKILL.md +23 -0
- package/skills/vue-router-best-practices/reference/router-beforeenter-no-param-trigger.md +167 -0
- package/skills/vue-router-best-practices/reference/router-beforerouteenter-no-this.md +176 -0
- package/skills/vue-router-best-practices/reference/router-guard-async-await-pattern.md +227 -0
- package/skills/vue-router-best-practices/reference/router-navigation-guard-infinite-loop.md +187 -0
- package/skills/vue-router-best-practices/reference/router-navigation-guard-next-deprecated.md +150 -0
- package/skills/vue-router-best-practices/reference/router-param-change-no-lifecycle.md +181 -0
- package/skills/vue-router-best-practices/reference/router-simple-routing-cleanup.md +209 -0
- package/skills/vue-router-best-practices/reference/router-use-vue-router-for-production.md +183 -0
- package/skills/vue-testing-best-practices/SKILL.md +29 -0
- package/skills/vue-testing-best-practices/reference/async-component-testing.md +163 -0
- package/skills/vue-testing-best-practices/reference/teleport-testing-complexity.md +158 -0
- package/skills/vue-testing-best-practices/reference/testing-async-await-flushpromises.md +175 -0
- package/skills/vue-testing-best-practices/reference/testing-browser-vs-node-runners.md +208 -0
- package/skills/vue-testing-best-practices/reference/testing-component-blackbox-approach.md +144 -0
- package/skills/vue-testing-best-practices/reference/testing-composables-helper-wrapper.md +238 -0
- package/skills/vue-testing-best-practices/reference/testing-e2e-playwright-recommended.md +242 -0
- package/skills/vue-testing-best-practices/reference/testing-no-snapshot-only.md +197 -0
- package/skills/vue-testing-best-practices/reference/testing-pinia-store-setup.md +228 -0
- package/skills/vue-testing-best-practices/reference/testing-suspense-async-components.md +229 -0
- package/skills/vue-testing-best-practices/reference/testing-vitest-recommended-for-vue.md +204 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: v-slot Can Only Be Used on Components or Template Tags
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Using v-slot on HTML elements causes compilation errors
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, slots, v-slot, compilation-error, common-mistake]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# v-slot Can Only Be Used on Components or Template Tags
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - The `v-slot` directive (and its shorthand `#`) can only be used on Vue components or `<template>` tags. Using it on native HTML elements like `<div>` or `<span>` causes a Vue compilation error.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Only use `v-slot` on component elements or `<template>` tags
|
|
16
|
+
- [ ] When using default scoped slot shorthand, apply to the component itself
|
|
17
|
+
- [ ] For named slots, always use `<template #name>` syntax
|
|
18
|
+
|
|
19
|
+
**Incorrect:**
|
|
20
|
+
```vue
|
|
21
|
+
<template>
|
|
22
|
+
<!-- BAD: v-slot on a native HTML element -->
|
|
23
|
+
<div v-slot:header>
|
|
24
|
+
<h1>Title</h1>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- BAD: Shorthand on HTML element -->
|
|
28
|
+
<span #default="{ item }">
|
|
29
|
+
{{ item.name }}
|
|
30
|
+
</span>
|
|
31
|
+
|
|
32
|
+
<!-- BAD: v-slot inside a plain HTML element -->
|
|
33
|
+
<div>
|
|
34
|
+
<p v-slot:content>Some text</p>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
These cause the error: `v-slot can only be used on components or <template> tags`
|
|
40
|
+
|
|
41
|
+
**Correct:**
|
|
42
|
+
```vue
|
|
43
|
+
<template>
|
|
44
|
+
<!-- GOOD: v-slot on component element (default scoped slot) -->
|
|
45
|
+
<MyComponent v-slot="{ item }">
|
|
46
|
+
{{ item.name }}
|
|
47
|
+
</MyComponent>
|
|
48
|
+
|
|
49
|
+
<!-- GOOD: Named slots use template tags -->
|
|
50
|
+
<BaseLayout>
|
|
51
|
+
<template #header>
|
|
52
|
+
<h1>Title</h1>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<template #default>
|
|
56
|
+
<p>Main content</p>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<template #footer>
|
|
60
|
+
<p>Footer content</p>
|
|
61
|
+
</template>
|
|
62
|
+
</BaseLayout>
|
|
63
|
+
|
|
64
|
+
<!-- GOOD: Shorthand on component for default slot -->
|
|
65
|
+
<FancyList #default="{ item }">
|
|
66
|
+
<div>{{ item.name }}</div>
|
|
67
|
+
</FancyList>
|
|
68
|
+
</template>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Common Scenarios
|
|
72
|
+
|
|
73
|
+
### Wrapping Slot Content in HTML
|
|
74
|
+
If you need HTML wrappers around slot content, put them inside the template:
|
|
75
|
+
|
|
76
|
+
```vue
|
|
77
|
+
<!-- WRONG -->
|
|
78
|
+
<MyComponent>
|
|
79
|
+
<div v-slot:header class="header-wrapper">
|
|
80
|
+
<h1>Title</h1>
|
|
81
|
+
</div>
|
|
82
|
+
</MyComponent>
|
|
83
|
+
|
|
84
|
+
<!-- CORRECT -->
|
|
85
|
+
<MyComponent>
|
|
86
|
+
<template #header>
|
|
87
|
+
<div class="header-wrapper">
|
|
88
|
+
<h1>Title</h1>
|
|
89
|
+
</div>
|
|
90
|
+
</template>
|
|
91
|
+
</MyComponent>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Multiple v-slot on Same Element
|
|
95
|
+
Another error occurs when you have multiple v-slot directives - only the first is recognized:
|
|
96
|
+
|
|
97
|
+
```vue
|
|
98
|
+
<!-- BAD: Multiple v-slot directives -->
|
|
99
|
+
<MyComponent v-slot:header v-slot:footer>
|
|
100
|
+
Content
|
|
101
|
+
</MyComponent>
|
|
102
|
+
|
|
103
|
+
<!-- GOOD: Separate template for each slot -->
|
|
104
|
+
<MyComponent>
|
|
105
|
+
<template #header>Header</template>
|
|
106
|
+
<template #footer>Footer</template>
|
|
107
|
+
</MyComponent>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Valid v-slot Locations
|
|
111
|
+
|
|
112
|
+
| Element Type | v-slot Allowed? | Example |
|
|
113
|
+
|--------------|-----------------|---------|
|
|
114
|
+
| Component | Yes | `<MyComponent v-slot="props">` |
|
|
115
|
+
| `<template>` | Yes | `<template #header>` |
|
|
116
|
+
| `<div>` | No | Compilation error |
|
|
117
|
+
| `<span>` | No | Compilation error |
|
|
118
|
+
| Any HTML element | No | Compilation error |
|
|
119
|
+
|
|
120
|
+
## Reference
|
|
121
|
+
- [Vue.js Slots](https://vuejs.org/guide/components/slots.html)
|
|
122
|
+
- [DeepScan - vue-misused-v-slot](https://deepscan.io/docs/rules/vue-misused-v-slot)
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Understand and Fix SSR Hydration Mismatches
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Hydration mismatches cause visual flickering, performance loss, and broken functionality
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, ssr, hydration, debugging, nuxt, server-side-rendering]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Understand and Fix SSR Hydration Mismatches
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - Hydration mismatches occur when the HTML rendered on the client differs from what the server rendered. Vue attempts to recover by discarding and re-rendering mismatched nodes, causing performance degradation, visual flickering, and potentially broken event handlers.
|
|
12
|
+
|
|
13
|
+
Understanding the common causes helps you prevent and debug these issues effectively.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Validate HTML structure for proper nesting (no div in p, no nested a tags)
|
|
18
|
+
- [ ] Move random value generation to onMounted or use seeded randoms
|
|
19
|
+
- [ ] Format dates/times on client side only
|
|
20
|
+
- [ ] Use `data-allow-mismatch` (Vue 3.5+) for intentional mismatches
|
|
21
|
+
- [ ] Check for browser-modified HTML in dev tools
|
|
22
|
+
|
|
23
|
+
## Cause 1: Invalid HTML Nesting
|
|
24
|
+
|
|
25
|
+
Browsers auto-correct invalid HTML, creating different DOM than Vue expects.
|
|
26
|
+
|
|
27
|
+
**Incorrect:**
|
|
28
|
+
```vue
|
|
29
|
+
<template>
|
|
30
|
+
<!-- WRONG: <div> cannot be inside <p> -->
|
|
31
|
+
<p>
|
|
32
|
+
<div>This will break hydration</div>
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
<!-- WRONG: <a> cannot be inside <a> -->
|
|
36
|
+
<a href="/parent">
|
|
37
|
+
<a href="/child">Nested link</a>
|
|
38
|
+
</a>
|
|
39
|
+
|
|
40
|
+
<!-- WRONG: Block elements in inline elements -->
|
|
41
|
+
<span>
|
|
42
|
+
<div>Block in inline</div>
|
|
43
|
+
</span>
|
|
44
|
+
</template>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Browser converts the first example to:
|
|
48
|
+
```html
|
|
49
|
+
<p></p>
|
|
50
|
+
<div>This will break hydration</div>
|
|
51
|
+
<p></p>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Correct:**
|
|
55
|
+
```vue
|
|
56
|
+
<template>
|
|
57
|
+
<!-- CORRECT: Use appropriate nesting -->
|
|
58
|
+
<div>
|
|
59
|
+
<div>This works fine</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<!-- CORRECT: Single link with event handling -->
|
|
63
|
+
<a href="/parent" @click="handleParentClick">
|
|
64
|
+
<span @click.stop="handleChildClick">Nested action</span>
|
|
65
|
+
</a>
|
|
66
|
+
|
|
67
|
+
<!-- CORRECT: Block element wrapper -->
|
|
68
|
+
<div>
|
|
69
|
+
<div>Block in block</div>
|
|
70
|
+
</div>
|
|
71
|
+
</template>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Cause 2: Random Values in Render
|
|
75
|
+
|
|
76
|
+
Server and client generate different random values.
|
|
77
|
+
|
|
78
|
+
**Incorrect:**
|
|
79
|
+
```vue
|
|
80
|
+
<template>
|
|
81
|
+
<!-- WRONG: Different ID on server vs client -->
|
|
82
|
+
<div :id="'field-' + Math.random()">
|
|
83
|
+
Form field
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<!-- WRONG: Random order differs -->
|
|
87
|
+
<div v-for="item in shuffledItems" :key="item.id">
|
|
88
|
+
{{ item.name }}
|
|
89
|
+
</div>
|
|
90
|
+
</template>
|
|
91
|
+
|
|
92
|
+
<script setup>
|
|
93
|
+
import { computed } from 'vue'
|
|
94
|
+
|
|
95
|
+
const items = [/* ... */]
|
|
96
|
+
|
|
97
|
+
// WRONG: Random shuffle runs differently on server and client
|
|
98
|
+
const shuffledItems = computed(() =>
|
|
99
|
+
[...items].sort(() => Math.random() - 0.5)
|
|
100
|
+
)
|
|
101
|
+
</script>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Correct - Client-Only Random:**
|
|
105
|
+
```vue
|
|
106
|
+
<template>
|
|
107
|
+
<div :id="fieldId">
|
|
108
|
+
Form field
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div v-for="item in displayItems" :key="item.id">
|
|
112
|
+
{{ item.name }}
|
|
113
|
+
</div>
|
|
114
|
+
</template>
|
|
115
|
+
|
|
116
|
+
<script setup>
|
|
117
|
+
import { ref, onMounted } from 'vue'
|
|
118
|
+
|
|
119
|
+
const items = [/* ... */]
|
|
120
|
+
|
|
121
|
+
// CORRECT: Start with deterministic value
|
|
122
|
+
const fieldId = ref('field-default')
|
|
123
|
+
const displayItems = ref(items) // Original order on server
|
|
124
|
+
|
|
125
|
+
onMounted(() => {
|
|
126
|
+
// Randomize only on client
|
|
127
|
+
fieldId.value = 'field-' + Math.random().toString(36).slice(2)
|
|
128
|
+
displayItems.value = [...items].sort(() => Math.random() - 0.5)
|
|
129
|
+
})
|
|
130
|
+
</script>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Correct - Seeded Random:**
|
|
134
|
+
```javascript
|
|
135
|
+
// utils/seededRandom.js
|
|
136
|
+
export function createSeededRandom(seed) {
|
|
137
|
+
return function() {
|
|
138
|
+
seed = (seed * 9301 + 49297) % 233280
|
|
139
|
+
return seed / 233280
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Use same seed on server and client
|
|
144
|
+
const seed = 12345 // Could be based on user ID, page, etc.
|
|
145
|
+
const random = createSeededRandom(seed)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Cause 3: Timezone and Date Differences
|
|
149
|
+
|
|
150
|
+
Server may be in different timezone than client.
|
|
151
|
+
|
|
152
|
+
**Incorrect:**
|
|
153
|
+
```vue
|
|
154
|
+
<template>
|
|
155
|
+
<!-- WRONG: Server time != client time -->
|
|
156
|
+
<span>{{ new Date().toLocaleTimeString() }}</span>
|
|
157
|
+
|
|
158
|
+
<!-- WRONG: Server formats dates in server's timezone -->
|
|
159
|
+
<span>{{ formatDate(article.createdAt) }}</span>
|
|
160
|
+
</template>
|
|
161
|
+
|
|
162
|
+
<script setup>
|
|
163
|
+
function formatDate(date) {
|
|
164
|
+
return new Date(date).toLocaleDateString()
|
|
165
|
+
}
|
|
166
|
+
</script>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Correct:**
|
|
170
|
+
```vue
|
|
171
|
+
<template>
|
|
172
|
+
<!-- CORRECT: Render placeholder, update on client -->
|
|
173
|
+
<span>{{ displayTime || 'Loading...' }}</span>
|
|
174
|
+
|
|
175
|
+
<!-- CORRECT: Use UTC or defer to client -->
|
|
176
|
+
<span>{{ formattedDate }}</span>
|
|
177
|
+
</template>
|
|
178
|
+
|
|
179
|
+
<script setup>
|
|
180
|
+
import { ref, computed, onMounted } from 'vue'
|
|
181
|
+
|
|
182
|
+
const props = defineProps(['article'])
|
|
183
|
+
const displayTime = ref(null)
|
|
184
|
+
const isClient = ref(false)
|
|
185
|
+
|
|
186
|
+
onMounted(() => {
|
|
187
|
+
displayTime.value = new Date().toLocaleTimeString()
|
|
188
|
+
isClient.value = true
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
// CORRECT: Server renders UTC, client converts to local
|
|
192
|
+
const formattedDate = computed(() => {
|
|
193
|
+
if (!props.article?.createdAt) return ''
|
|
194
|
+
|
|
195
|
+
if (isClient.value) {
|
|
196
|
+
// Client: user's local timezone
|
|
197
|
+
return new Date(props.article.createdAt).toLocaleDateString()
|
|
198
|
+
} else {
|
|
199
|
+
// Server: consistent UTC format
|
|
200
|
+
return new Date(props.article.createdAt).toISOString().split('T')[0]
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
</script>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Cause 4: Browser Extensions and Modifications
|
|
207
|
+
|
|
208
|
+
Browser extensions can inject content into the DOM.
|
|
209
|
+
|
|
210
|
+
**Mitigation:**
|
|
211
|
+
```vue
|
|
212
|
+
<template>
|
|
213
|
+
<!-- Use data-allow-mismatch for areas extensions might modify -->
|
|
214
|
+
<head data-allow-mismatch>
|
|
215
|
+
<title>{{ pageTitle }}</title>
|
|
216
|
+
</head>
|
|
217
|
+
</template>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Vue 3.5+ Suppressing Intentional Mismatches
|
|
221
|
+
|
|
222
|
+
```vue
|
|
223
|
+
<template>
|
|
224
|
+
<!-- Suppress specific mismatch types -->
|
|
225
|
+
<div data-allow-mismatch="text">
|
|
226
|
+
{{ clientOnlyText }}
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
<!-- Suppress all mismatches for this element -->
|
|
230
|
+
<div data-allow-mismatch>
|
|
231
|
+
<ComplexClientComponent />
|
|
232
|
+
</div>
|
|
233
|
+
</template>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Valid `data-allow-mismatch` values:
|
|
237
|
+
- `text` - Text content mismatches
|
|
238
|
+
- `children` - Child element mismatches
|
|
239
|
+
- `class` - Class attribute mismatches
|
|
240
|
+
- `style` - Style attribute mismatches
|
|
241
|
+
- `attribute` - Other attribute mismatches
|
|
242
|
+
- (no value) - All mismatches
|
|
243
|
+
|
|
244
|
+
## Debugging Hydration Mismatches
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
// Enable detailed hydration mismatch warnings in development
|
|
248
|
+
// vite.config.js
|
|
249
|
+
export default {
|
|
250
|
+
define: {
|
|
251
|
+
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: true
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```vue
|
|
257
|
+
<script setup>
|
|
258
|
+
import { onMounted } from 'vue'
|
|
259
|
+
|
|
260
|
+
// Debug: Compare server HTML with client expectation
|
|
261
|
+
onMounted(() => {
|
|
262
|
+
const serverHTML = document.getElementById('app').innerHTML
|
|
263
|
+
console.log('Server rendered:', serverHTML)
|
|
264
|
+
})
|
|
265
|
+
</script>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Common Error Messages
|
|
269
|
+
|
|
270
|
+
| Error | Likely Cause |
|
|
271
|
+
|-------|--------------|
|
|
272
|
+
| "Hydration text content mismatch" | Different text on server/client (dates, random) |
|
|
273
|
+
| "Hydration children mismatch" | Invalid HTML nesting, conditional rendering |
|
|
274
|
+
| "Hydration attribute mismatch" | Dynamic attributes with different values |
|
|
275
|
+
| "Hydration node mismatch" | Completely different elements rendered |
|
|
276
|
+
|
|
277
|
+
## Reference
|
|
278
|
+
- [Vue.js SSR Guide - Hydration Mismatch](https://vuejs.org/guide/scaling-up/ssr.html#hydration-mismatch)
|
|
279
|
+
- [Nuxt Hydration Best Practices](https://nuxt.com/docs/guide/best-practices/hydration)
|
|
280
|
+
- [data-allow-mismatch RFC](https://github.com/vuejs/core/pull/9562)
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Guard Platform-Specific APIs in Universal SSR Code
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Accessing browser-only APIs on server causes crashes; Node.js APIs fail in browser
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, ssr, browser-api, nodejs, universal, isomorphic, server-side-rendering]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Guard Platform-Specific APIs in Universal SSR Code
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - SSR applications run the same code on both server (Node.js) and client (browser). Browser APIs like `window`, `document`, and `localStorage` don't exist in Node.js and will throw `ReferenceError`. Similarly, Node.js APIs like `fs` and `process` aren't available in browsers.
|
|
12
|
+
|
|
13
|
+
Universal/isomorphic code must guard platform-specific API access or use libraries that work on both platforms.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Never access `window`, `document`, `navigator` in `setup()` or `created()`
|
|
18
|
+
- [ ] Move browser API access to `onMounted()` lifecycle hook
|
|
19
|
+
- [ ] Use `typeof window !== 'undefined'` guard when needed outside lifecycle
|
|
20
|
+
- [ ] Use cross-platform libraries for common functionality (fetch, storage)
|
|
21
|
+
- [ ] Use Nuxt's `process.client` / `process.server` guards in Nuxt projects
|
|
22
|
+
|
|
23
|
+
## Common Browser APIs That Break SSR
|
|
24
|
+
|
|
25
|
+
| API | Node.js Behavior |
|
|
26
|
+
|-----|-----------------|
|
|
27
|
+
| `window` | `ReferenceError: window is not defined` |
|
|
28
|
+
| `document` | `ReferenceError: document is not defined` |
|
|
29
|
+
| `localStorage` / `sessionStorage` | `ReferenceError` |
|
|
30
|
+
| `navigator` | `ReferenceError` |
|
|
31
|
+
| `location` | `ReferenceError` |
|
|
32
|
+
| `history` | `ReferenceError` |
|
|
33
|
+
| `alert` / `confirm` / `prompt` | `ReferenceError` |
|
|
34
|
+
| `requestAnimationFrame` | `ReferenceError` |
|
|
35
|
+
| `IntersectionObserver` | `ReferenceError` |
|
|
36
|
+
| `ResizeObserver` | `ReferenceError` |
|
|
37
|
+
|
|
38
|
+
**Incorrect - Crashes on Server:**
|
|
39
|
+
```javascript
|
|
40
|
+
// WRONG: These run during setup/SSR - crashes in Node.js
|
|
41
|
+
const width = ref(window.innerWidth)
|
|
42
|
+
const theme = localStorage.getItem('theme')
|
|
43
|
+
const userAgent = navigator.userAgent
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```vue
|
|
47
|
+
<script setup>
|
|
48
|
+
import { ref } from 'vue'
|
|
49
|
+
|
|
50
|
+
// WRONG: Runs on server, crashes
|
|
51
|
+
const scrollY = ref(window.scrollY)
|
|
52
|
+
|
|
53
|
+
// WRONG: document doesn't exist on server
|
|
54
|
+
document.title = 'My Page'
|
|
55
|
+
</script>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Correct - Use onMounted:**
|
|
59
|
+
```vue
|
|
60
|
+
<script setup>
|
|
61
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
62
|
+
|
|
63
|
+
// Safe defaults that work on server
|
|
64
|
+
const width = ref(0)
|
|
65
|
+
const theme = ref('light')
|
|
66
|
+
const scrollY = ref(0)
|
|
67
|
+
|
|
68
|
+
onMounted(() => {
|
|
69
|
+
// Browser APIs only accessed after mount (client-only)
|
|
70
|
+
width.value = window.innerWidth
|
|
71
|
+
theme.value = localStorage.getItem('theme') || 'light'
|
|
72
|
+
scrollY.value = window.scrollY
|
|
73
|
+
|
|
74
|
+
// Event listeners safe in mounted
|
|
75
|
+
window.addEventListener('resize', handleResize)
|
|
76
|
+
window.addEventListener('scroll', handleScroll)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
onUnmounted(() => {
|
|
80
|
+
window.removeEventListener('resize', handleResize)
|
|
81
|
+
window.removeEventListener('scroll', handleScroll)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
function handleResize() {
|
|
85
|
+
width.value = window.innerWidth
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function handleScroll() {
|
|
89
|
+
scrollY.value = window.scrollY
|
|
90
|
+
}
|
|
91
|
+
</script>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Correct - Guard with typeof:**
|
|
95
|
+
```javascript
|
|
96
|
+
// When you need to check outside lifecycle hooks
|
|
97
|
+
function getStoredValue(key, defaultValue) {
|
|
98
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
99
|
+
return localStorage.getItem(key) ?? defaultValue
|
|
100
|
+
}
|
|
101
|
+
return defaultValue
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Composable with SSR awareness
|
|
105
|
+
export function useMediaQuery(query) {
|
|
106
|
+
const matches = ref(false)
|
|
107
|
+
|
|
108
|
+
// Only run on client
|
|
109
|
+
if (typeof window !== 'undefined') {
|
|
110
|
+
const mediaQuery = window.matchMedia(query)
|
|
111
|
+
matches.value = mediaQuery.matches
|
|
112
|
+
|
|
113
|
+
// Setup listener in lifecycle
|
|
114
|
+
onMounted(() => {
|
|
115
|
+
const handler = (e) => { matches.value = e.matches }
|
|
116
|
+
mediaQuery.addEventListener('change', handler)
|
|
117
|
+
onUnmounted(() => mediaQuery.removeEventListener('change', handler))
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return matches
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Nuxt.js Guards
|
|
126
|
+
|
|
127
|
+
```vue
|
|
128
|
+
<script setup>
|
|
129
|
+
// Nuxt provides process.client and process.server
|
|
130
|
+
if (process.client) {
|
|
131
|
+
// Only runs in browser
|
|
132
|
+
window.analytics.track('page_view')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (process.server) {
|
|
136
|
+
// Only runs on server
|
|
137
|
+
console.log('Rendering on server')
|
|
138
|
+
}
|
|
139
|
+
</script>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```vue
|
|
143
|
+
<template>
|
|
144
|
+
<!-- ClientOnly component for client-only rendering -->
|
|
145
|
+
<ClientOnly>
|
|
146
|
+
<BrowserOnlyChart :data="chartData" />
|
|
147
|
+
<template #fallback>
|
|
148
|
+
<ChartSkeleton />
|
|
149
|
+
</template>
|
|
150
|
+
</ClientOnly>
|
|
151
|
+
</template>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Cross-Platform Libraries
|
|
155
|
+
|
|
156
|
+
Use libraries that abstract platform differences:
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
// Fetch - works in both Node.js 18+ and browsers
|
|
160
|
+
const response = await fetch('/api/data')
|
|
161
|
+
|
|
162
|
+
// For older Node.js, use node-fetch or axios
|
|
163
|
+
import axios from 'axios'
|
|
164
|
+
const { data } = await axios.get('/api/data')
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
// Universal cookie handling
|
|
169
|
+
import Cookies from 'js-cookie' // Client only
|
|
170
|
+
import { parse } from 'cookie' // Works both
|
|
171
|
+
|
|
172
|
+
// In Nuxt, use useCookie()
|
|
173
|
+
const token = useCookie('auth-token')
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Common Node.js APIs That Break in Browser
|
|
177
|
+
|
|
178
|
+
| API | Browser Behavior |
|
|
179
|
+
|-----|-----------------|
|
|
180
|
+
| `fs` | Module not found |
|
|
181
|
+
| `path` | Module not found |
|
|
182
|
+
| `process` (full) | Undefined or limited |
|
|
183
|
+
| `Buffer` | Undefined (unless polyfilled) |
|
|
184
|
+
| `__dirname` / `__filename` | Undefined |
|
|
185
|
+
| `require()` | Undefined in ES modules |
|
|
186
|
+
|
|
187
|
+
**Incorrect:**
|
|
188
|
+
```javascript
|
|
189
|
+
// WRONG: Node.js APIs in universal code
|
|
190
|
+
import fs from 'fs'
|
|
191
|
+
const config = JSON.parse(fs.readFileSync('./config.json'))
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Correct - Separate Server Code:**
|
|
195
|
+
```javascript
|
|
196
|
+
// server/utils.js - Server-only file
|
|
197
|
+
import fs from 'fs'
|
|
198
|
+
export function loadConfig() {
|
|
199
|
+
return JSON.parse(fs.readFileSync('./config.json'))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// app.js - Universal code uses API instead
|
|
203
|
+
const config = await fetch('/api/config').then(r => r.json())
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Environment Detection Utility
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
// utils/environment.js
|
|
210
|
+
export const isClient = typeof window !== 'undefined'
|
|
211
|
+
export const isServer = !isClient
|
|
212
|
+
|
|
213
|
+
export const isBrowser = isClient && typeof document !== 'undefined'
|
|
214
|
+
export const isNode = typeof process !== 'undefined' &&
|
|
215
|
+
process.versions?.node != null
|
|
216
|
+
|
|
217
|
+
// Usage
|
|
218
|
+
import { isClient, isServer } from '@/utils/environment'
|
|
219
|
+
|
|
220
|
+
if (isClient) {
|
|
221
|
+
// Browser-specific code
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Third-Party Library Issues
|
|
226
|
+
|
|
227
|
+
Some libraries auto-access browser APIs on import:
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
// WRONG: Library accesses window on import
|
|
231
|
+
import SomeChartLibrary from 'some-chart-library'
|
|
232
|
+
// ^ Crashes on server if library does: const x = window.something
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Correct - Dynamic Import:**
|
|
236
|
+
```vue
|
|
237
|
+
<script setup>
|
|
238
|
+
import { defineAsyncComponent } from 'vue'
|
|
239
|
+
|
|
240
|
+
// Dynamic import only loads on client
|
|
241
|
+
const Chart = defineAsyncComponent(() =>
|
|
242
|
+
import('some-chart-library').then(m => m.ChartComponent)
|
|
243
|
+
)
|
|
244
|
+
</script>
|
|
245
|
+
|
|
246
|
+
<template>
|
|
247
|
+
<ClientOnly>
|
|
248
|
+
<Chart :data="data" />
|
|
249
|
+
</ClientOnly>
|
|
250
|
+
</template>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Reference
|
|
254
|
+
- [Vue.js SSR - Platform-Specific APIs](https://vuejs.org/guide/scaling-up/ssr.html#access-to-platform-specific-apis)
|
|
255
|
+
- [Nuxt ClientOnly Component](https://nuxt.com/docs/api/components/client-only)
|
|
256
|
+
- [MDN: Web APIs](https://developer.mozilla.org/en-US/docs/Web/API)
|