Re-building Notion Pages with the API: How the Block-Based Model Powers Flexibility and Why It Can Hurt Performance
Notion’s block-based architecture gives you unmatched flexibility, but it can make page retrieval slow. Learn how the model works, see a concrete API example, and discover practical ways to keep performance high.
09 Feb 2026, 15:28 UTC

What Problem Does the Block Model Solve?
When you open a Notion page, you see a smooth, scroll-through interface. Behind the scenes, every paragraph, image, table, or database is a block. This means the editor stores content as a list of independent objects rather than a single monolithic document. The benefit is immense flexibility: you can embed a database, nest a table inside a text block, or link a page to another without rewriting the whole page. The downside is that pulling a page out of Notion via the API involves walking a tree of block IDs, which can be slow for deeply nested structures.
Thesis: Flexibility at the Cost of Retrieval Complexity
Notion’s core design separates data storage from presentation. The same underlying blocks are rendered differently in Table, Board, or Calendar views. This separation allows you to keep your data in one place and change how you look at it. However, because the editor does not enforce a relational schema, you cannot rely on traditional SQL constraints to validate data. Instead, you must design your block hierarchy carefully to avoid performance hits.
How Blocks Are Stored
Every block has a unique id and a type (e.g., paragraph, image, table, database). Blocks can contain child blocks, creating a recursive tree. The API exposes this structure via the /v1/blocks/{block_id} endpoint. A typical response looks like:
{
"object": "block",
"id": "abcd-1234",
"type": "paragraph",
"paragraph": {
"rich_text": [ ... ]
},
"has_children": true
}
If has_children is true, you must call /v1/blocks/{block_id}/children to fetch them. This “walk-the-tree” pattern is the source of latency when a page contains hundreds of nested blocks.
Decoupling Data from Presentation with Database Views
Notion stores database entries as blocks of type database. Each entry is itself a block that contains child blocks for each property. When you switch a database view from Table to Board, Notion does not rewrite the underlying data; it simply applies a different filter and layout on the same set of blocks. This design is powerful: you can keep a single data source and expose it in multiple ways.
For example, a marketing team may maintain a single “Campaigns” database and view it as a Table for reporting, a Board for Kanban, and a Calendar for deadlines. All views share the same underlying blocks, so updates in one view immediately appear in the others.
Practical Example: Reconstructing a Page with the API
Below is a minimal Node.js script that fetches a page and prints a simplified tree of block types. Replace <YOUR_TOKEN> with a workspace-level integration token and <PAGE_ID> with the target page’s ID. This script should be run in a Node.js environment with the node-fetch package installed.
const fetch = require('node-fetch');
const TOKEN = '<YOUR_TOKEN>';
const PAGE_ID = '<PAGE_ID>';
async function getBlock(id) {
const res = await fetch(`https://api.notion.com/v1/blocks/${id}`, {
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Notion-Version': '2022-06-28',
'Accept': 'application/json'
}
});
return await res.json();
}
async function walkBlock(id, depth = 0) {
const block = await getBlock(id);
console.log(' '.repeat(depth) + block.type);
if (block.has_children) {
const childrenRes = await fetch(`https://api.notion.com/v1/blocks/${id}/children`, {
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Notion-Version': '2022-06-28',
'Accept': 'application/json'
}
});
const children = await childrenRes.json();
for (const child of children.results) {
await walkBlock(child.id, depth + 1);
}
}
}
walkBlock(PAGE_ID);
Running this script will output a tree like:
page
heading_2
paragraph
table
column
image
Notice how the script must fetch each child block individually. For a page with 200 nested blocks, the API will make roughly 200+ calls, which can exceed rate limits or take several seconds to complete.
Trade-Off: Performance vs. Flexibility
Because Notion does not enforce a rigid schema, you can create arbitrary nested structures. However, the recursive nature of blocks means that:
- Page load times increase linearly with the number of child blocks.
- API retrieval can hit rate limits if you naïvely fetch every child without batching.
- Large databases (thousands of rows) may cause UI lag when switching views, because Notion must re-render the same underlying blocks with new filters.
To mitigate these issues, consider flattening deep nesting where possible, using the block_children pagination endpoint to fetch children in batches, and storing heavy data in external services and linking to them rather than embedding every cell as a Notion block.
Conclusion: Design Your Notion Data Model for the API
If you plan to expose Notion content to external apps, treat the block tree as a graph that you must traverse. Keep nesting shallow, batch API calls, and use database views to separate concerns. By doing so, you preserve Notion’s flexibility while keeping external integrations responsive.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.