Blocks

Component Architecture

By default, Vextro promotes all block tables into a dedicated Convex component (vextroBlocks), separate from your main application tables. This keeps your dashboard organized as the number of block types grows.

Why a separate component?

Dashboard clarity

Projects with 30-40 block types would otherwise crowd your main Convex dashboard. Grouping them under vextroBlocks keeps the main namespace focused on your application tables.

Encapsulation

All block CRUD operations go through a well-defined API boundary. Internal table structure, indexes, and system fields are implementation details that do not leak into your application code.

Independent scaling

Component tables have their own indexes and do not compete with main-app queries for read bandwidth. As block volume grows, performance stays predictable.

Performance

Benchmarks run against a cloud Convex deployment with 35 block tables, 50 iterations per test:

TestBaselineCross-ComponentDeltaThreshold
Single block fetch~6ms~16ms+10ms≤20ms
Batch fetch (10 blocks)~1.6ms~1.9ms+0.3ms≤50ms
Batch fetch (50 blocks)~3.1ms~4.0ms+1.0ms≤50ms
Batch fetch (100 blocks)~5.3ms~7.0ms+1.7ms≤50ms
Index scan (50 results)~1.8ms~1.7ms-0.2ms≤50ms
Tree traversal (21 nodes)~5.4ms~4.1ms-1.3ms≤50ms

Key takeaways:

  • Single fetch adds roughly 10ms of overhead -- within the 20ms threshold and imperceptible in page loads.
  • Batch operations add 0-2ms regardless of batch size, well under the 50ms threshold.
  • Index scans and tree traversals show no measurable overhead; component isolation does not degrade complex queries.

The benchmark harness is available at benchmarks/block-component-perf/.

Type safety

When blocks are stored in the component, the blockId field on parent references is typed as string rather than Id<"heroBlocks">. This is the only type-safety trade-off of component hosting.

All other surfaces remain fully typed:

  • defineVextroBlock() field definitions
  • f.blocks() field builder
  • blockRef() nested block references
  • children block composition
  • commonOptions shared block options

The blockType discriminant on each block reference enables exhaustive type narrowing when processing blocks at runtime.

Opting out

If your project has few block types or you need full v.id() type safety on block references, you can keep block tables in your main Convex namespace on a per-collection basis. See Collection Opt-In for configuration details.

Previous
Collection Opt-In