mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-07 20:47:37 +00:00
shell: fish functions -> ~/.local/bin executables + bash/zsh ports; manage ~/.agents
- 16 cross-shell utilities converted to sh/bash chezmoi executables: ai c2p cp-appid extract_agents fab gdc gh-sonr-org-migrate ghg-clone ghn gho git-summary launch_omp_livediff_overlay lc-elir mdcp mdp pj (ghg-clone not named ghg: unrelated cargo binary owns that name) - cwd/history helpers stay per-shell: yy c cd clip-last + ghg/ghn/gho cd-wrappers in bash/functions/ and zsh/functions/, loaded by rc files - fish keeps only shell-state functions; converted ones now hit PATH - cp-appid ignore gating moved to .local/bin path - dot_agents/skills: 82 skills materialized from dotstate storage so dot_claude skill symlinks resolve on fresh bootstrap; dotstate profile/symlinks.json released .agents/skills .bashrc .zshrc
This commit is contained in:
1 parent
6776366ce6
commit
6406208b35
637 files changed
+100297
-497
No files matched your search
@@ -0,0 +1,158 @@
|
||||
# ssr-dehydration: Use Dehydrate/Hydrate Pattern for SSR
|
||||
|
||||
## Priority: MEDIUM
|
||||
|
||||
## Explanation
|
||||
|
||||
For server-side rendering, prefetch queries on the server, dehydrate the cache to a serializable format, send it to the client, and hydrate on the client. This prevents content flash and duplicate requests.
|
||||
|
||||
## Bad Example
|
||||
|
||||
```tsx
|
||||
// No SSR data passing - client refetches everything
|
||||
// server-side
|
||||
export async function getServerSideProps() {
|
||||
const data = await fetchPosts()
|
||||
return { props: { posts: data } } // Bypasses React Query cache
|
||||
}
|
||||
|
||||
// client-side
|
||||
function PostsPage({ posts }: { posts: Post[] }) {
|
||||
// This doesn't benefit from the server fetch
|
||||
const { data } = useQuery({
|
||||
queryKey: ['posts'],
|
||||
queryFn: fetchPosts,
|
||||
// Will refetch on client, causing flash
|
||||
})
|
||||
|
||||
return <PostList posts={data ?? posts} /> // Awkward fallback pattern
|
||||
}
|
||||
```
|
||||
|
||||
## Good Example: Next.js App Router
|
||||
|
||||
```tsx
|
||||
// app/posts/page.tsx
|
||||
import {
|
||||
dehydrate,
|
||||
HydrationBoundary,
|
||||
QueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
import { postQueries } from '@/lib/queries'
|
||||
|
||||
export default async function PostsPage() {
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
await queryClient.prefetchQuery(postQueries.list())
|
||||
|
||||
return (
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<PostList />
|
||||
</HydrationBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
// components/PostList.tsx
|
||||
'use client'
|
||||
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { postQueries } from '@/lib/queries'
|
||||
|
||||
export function PostList() {
|
||||
const { data: posts } = useSuspenseQuery(postQueries.list())
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{posts.map(post => (
|
||||
<li key={post.id}>{post.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Good Example: TanStack Start/Router
|
||||
|
||||
```tsx
|
||||
// routes/posts.tsx
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { postQueries } from '@/lib/queries'
|
||||
|
||||
export const Route = createFileRoute('/posts')({
|
||||
loader: async ({ context: { queryClient } }) => {
|
||||
// Prefetch in route loader
|
||||
await queryClient.ensureQueryData(postQueries.list())
|
||||
},
|
||||
component: PostsPage,
|
||||
})
|
||||
|
||||
function PostsPage() {
|
||||
const { data: posts } = useSuspenseQuery(postQueries.list())
|
||||
return <PostList posts={posts} />
|
||||
}
|
||||
```
|
||||
|
||||
## Good Example: Manual SSR Setup
|
||||
|
||||
```tsx
|
||||
// server.tsx
|
||||
import { dehydrate, QueryClient } from '@tanstack/react-query'
|
||||
import { renderToString } from 'react-dom/server'
|
||||
|
||||
export async function render(url: string) {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 60 * 1000, // Prevent immediate client refetch
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Prefetch required data
|
||||
await queryClient.prefetchQuery({
|
||||
queryKey: ['posts'],
|
||||
queryFn: fetchPosts,
|
||||
})
|
||||
|
||||
const dehydratedState = dehydrate(queryClient)
|
||||
|
||||
const html = renderToString(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
// Serialize safely - JSON.stringify is XSS vulnerable
|
||||
const serializedState = serialize(dehydratedState)
|
||||
|
||||
return `
|
||||
<html>
|
||||
<body>
|
||||
<div id="app">${html}</div>
|
||||
<script>window.__DEHYDRATED_STATE__ = ${serializedState}</script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
}
|
||||
|
||||
// client.tsx
|
||||
import { hydrate, QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
hydrate(queryClient, window.__DEHYDRATED_STATE__)
|
||||
|
||||
hydrateRoot(
|
||||
document.getElementById('app'),
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
</QueryClientProvider>
|
||||
)
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
- Create new QueryClient per request to prevent data sharing between users
|
||||
- Set `staleTime > 0` on server to prevent immediate client refetch
|
||||
- Use a safe serializer (not JSON.stringify) to prevent XSS
|
||||
- Failed queries aren't dehydrated by default; use `shouldDehydrateQuery` to override
|
||||
- `HydrationBoundary` can be nested for route-level prefetching
|
||||
Reference in new issue
Block a user