- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma binaryTargets for openssl 3.0 - nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal cron, http->https redirect - prisma: add origin_goods.delisted migration, sync missing schema (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz - api: CORS wildcard reflection, helmet CORP cross-origin, price backfill in persistProductDetail, categoryIcon ancestor fallback, mediaByColor per-color gallery in public goods detail - admin: /admin base path (vite + router) - import-data.mjs: udt_name casting, serial sequence advance fix
98 lines
2.6 KiB
Markdown
98 lines
2.6 KiB
Markdown
---
|
|
title: Async Component Best Practices
|
|
impact: MEDIUM
|
|
impactDescription: Poor async component strategy can delay interactivity in SSR apps and create loading UI flicker
|
|
type: best-practice
|
|
tags: [vue3, async-components, ssr, hydration, performance, ux]
|
|
---
|
|
|
|
# Async Component Best Practices
|
|
|
|
**Impact: MEDIUM** - Async components should reduce JavaScript cost without degrading perceived performance. Focus on hydration timing in SSR and stable loading UX.
|
|
|
|
## Task List
|
|
|
|
- Use lazy hydration strategies for non-critical SSR component trees
|
|
- Import only the hydration helpers you actually use
|
|
- Keep `loadingComponent` delay near the default `200ms` unless real UX data suggests otherwise
|
|
- Configure `delay` and `timeout` together for predictable loading behavior
|
|
|
|
## Use Lazy Hydration Strategies in SSR
|
|
|
|
In Vue 3.5+, async components can delay hydration until idle time, visibility, media query match, or user interaction.
|
|
|
|
**BAD:**
|
|
```vue
|
|
<script setup lang="ts">
|
|
import { defineAsyncComponent } from 'vue'
|
|
|
|
const AsyncComments = defineAsyncComponent({
|
|
loader: () => import('./Comments.vue')
|
|
})
|
|
</script>
|
|
```
|
|
|
|
**GOOD:**
|
|
```vue
|
|
<script setup lang="ts">
|
|
import {
|
|
defineAsyncComponent,
|
|
hydrateOnVisible,
|
|
hydrateOnIdle
|
|
} from 'vue'
|
|
|
|
const AsyncComments = defineAsyncComponent({
|
|
loader: () => import('./Comments.vue'),
|
|
hydrate: hydrateOnVisible({ rootMargin: '100px' })
|
|
})
|
|
|
|
const AsyncFooter = defineAsyncComponent({
|
|
loader: () => import('./Footer.vue'),
|
|
hydrate: hydrateOnIdle(5000)
|
|
})
|
|
</script>
|
|
```
|
|
|
|
## Prevent Loading Spinner Flicker
|
|
|
|
Avoid showing loading UI immediately for components that usually resolve quickly.
|
|
|
|
**BAD:**
|
|
```vue
|
|
<script setup lang="ts">
|
|
import { defineAsyncComponent } from 'vue'
|
|
import LoadingSpinner from './LoadingSpinner.vue'
|
|
|
|
const AsyncDashboard = defineAsyncComponent({
|
|
loader: () => import('./Dashboard.vue'),
|
|
loadingComponent: LoadingSpinner,
|
|
delay: 0
|
|
})
|
|
</script>
|
|
```
|
|
|
|
**GOOD:**
|
|
```vue
|
|
<script setup lang="ts">
|
|
import { defineAsyncComponent } from 'vue'
|
|
import LoadingSpinner from './LoadingSpinner.vue'
|
|
import ErrorDisplay from './ErrorDisplay.vue'
|
|
|
|
const AsyncDashboard = defineAsyncComponent({
|
|
loader: () => import('./Dashboard.vue'),
|
|
loadingComponent: LoadingSpinner,
|
|
errorComponent: ErrorDisplay,
|
|
delay: 200,
|
|
timeout: 30000
|
|
})
|
|
</script>
|
|
```
|
|
|
|
## Delay Guidelines
|
|
|
|
| Scenario | Recommended Delay |
|
|
|----------|-------------------|
|
|
| Small component, fast network | `200ms` |
|
|
| Known heavy component | `100ms` |
|
|
| Background or non-critical UI | `300-500ms` |
|