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:
| Test | Baseline | Cross-Component | Delta | Threshold |
|---|---|---|---|---|
| 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 definitionsf.blocks()field builderblockRef()nested block referenceschildrenblock compositioncommonOptionsshared 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.