Pages: loader props lose `Date` (and other non-JSON types) and their types don't say so
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 35/100
- issue の種類
- 機能追加
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
- 技術スタック
- typescript
- 領域
- full-stack
調査の方向性
Start by reading pages/client.mjs and the server serialization entry points named in the issue: serializePageData, streamDeferredResponse, and the page-object responses. Trace how initial page data, deferred chunks, navigation, and action responses are serialized and parsed, then compare the behavior with the existing Serialize<T> fetch-client types. Done means the selected transport and InferProps types agree for supported values, with coverage for server rendering, hydration, and later navigations.
索引モデルが issue の本文から書いたものです。
説明
Pages: loader props lose Date (and other non-JSON types) and their types don't say so
What happens
Page data is sent as plain JSON, but InferProps types the props as the loader's raw return type. A loader that returns a Drizzle row with a timestamp_ms column (every Void auth table has these) gives the page a prop typed Date, which is only a Date during the server render:
| Where | createdAt is |
|---|---|
InferProps<typeof loader> |
Date |
| Server render | Date (raw loader output) |
| Browser, hydration and later navigations/actions | string (ISO, from JSON.parse) |
Code that type-checks then crashes in the browser. For example, Intl.DateTimeFormat#format(note.createdAt) renders on the server, then throws RangeError: Invalid time value during hydration.
// pages/notes.server.ts
export type Props = InferProps<typeof loader>;
export const loader = defineHandler(async () => ({
notes: await db.select().from(noteTable), // createdAt: Date
}));
// pages/notes.tsx
const fmt = new Intl.DateTimeFormat('en', { dateStyle: 'medium' });
export default function Notes({ notes }: Props) {
return notes.map((n) => <p key={n.id}>{fmt.format(n.createdAt)}</p>);
// server: renders. browser: RangeError: Invalid time value
}
The same happens with Map/Set (they become {}), undefined fields (dropped), and bigint (throws).
Today the workaround is to convert every timestamp at the loader (createdAt: row.createdAt.getTime()).
The typed fetch client avoids the type problem by wrapping responses in Serialize<T> (Date → string), as described in the type-safety guide. Page props, which travel through the same JSON, don't get that treatment. Every meta-framework Void integrates with keeps Date in page data: SvelteKit and Nuxt (devalue), React Router (turbo-stream), TanStack Start (seroval). So this only affects native Void pages.
Proposal: serialize page data with devalue
devalue.stringify / devalue.parse preserve Date, Map, Set, bigint, undefined, RegExp, URL and repeated or cyclic references. The output is still JSON text, so it fits the existing <script type="application/json"> payload and escapeScriptJson. parse never evaluates code. SvelteKit and Nuxt use it for exactly this job.
The JSON calls that would need to switch (from the built [email protected] dist):
- Server:
serializePageDataand the frontmatter script (serialize-*.mjs)- the page-object and deferred-chunk
JSON.stringifycalls instreamDeferredResponse(protocol-*.mjs) - the JSON page-object response for client navigations and actions
- Client (
pages/client.mjs):readInitialPageDataparsePageDataFromHtml- the navigation and action
response.json()reads clonePageData(could becomestructuredClone, which also keepsDate)
After the change:
- The server render and the browser see the same values.
InferPropsstays the raw return type, which is then accurate for everything devalue supports.- Loaders can return Drizzle rows directly.
Values devalue can't handle (functions, class instances) could be stripped from InferProps with a small mapped type, the way Serialize<T> already strips functions.
Alternatives
- Minimal fix, no format change: apply
Serialize<T>toInferProps, and render the server page fromJSON.parse(JSON.stringify(props)). Types and runtime would then agree, but dates stay strings. - seroval: covers more types and can also stream Promises, so it could replace the custom
defer()streaming. It's a bigger change, and some of its output modes are JavaScript that the browser evaluates.
Related
- #478:
InferPropsreads__outputdirectly, while routes wrap it inSerialize<…>. - #443: RSC for pages would change the transport for the React adapter. Page data is JSON (NDJSON for deferred props) today.
- 主要言語
- TypeScript
- スター
- 580
- フォーク
- 56
- 平均マージ
- 11時間 28分
- マージ済み PR(30日)
- 150
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
voidzero-dev/void のほかの issue
-
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
voidzero-dev/void#619 ·
メンテナーはふだん 1 日以内に返信
-
@void/react: client environment has no optimizeDeps.entries for pages, causing a dev re-optimization reload on first load対応中かも @0froq が今日担当しました。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 66/100
voidzero-dev/void#599 · コメント 1 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
`void/log`: add `logger.child(fields)` to bind fields to every line対応中かも @Dazzanova が今日担当しました。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 82/100
voidzero-dev/void#596 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 56/100
voidzero-dev/void#579 ·
メンテナーはふだん 1 日以内に返信
-
難易度 3/5 1〜2日 初心者へのやさしさ 55/100
voidzero-dev/void#573 ·
メンテナーはふだん 1 日以内に返信
voidzero-dev/void の issue をすべて見る
似ている issue
-
area: server priority: P3 type: bug
難易度 2/5 1〜3時間 初心者へのやさしさ 83/100
appandflow/stim#4538 ·
メンテナーはふだん 1 日以内に返信
-
[BUG] Published Docker images lack the redis extra: HEADROOM_COMPRESS_SESSION_STORE stops startup対応中かも @AlessandroSpallina が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
headroomlabs-ai/headroom#4513 ·
メンテナーはふだん 1 日以内に返信
-
bug extension extension: obsidian platform: Windows
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
raycast/extensions#32838 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
copy design-review mac P3
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
tosunlab/hisarapp-core#510 ·
-
bug priority-medium to-agent
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信