I am building an internal analytics plancia (dashboard) for an SME to compare sales performance across two different periods (Year-Over-Year / Period-Over-Period comparison) - selecting 1 customer or to overview analytics of the articles.
My Current Stack & Workflow Architecture:
Frontend: A simple browser-based HTML page generated and served via an n8n Webhook Node (using Bootstrap, HTML5 inputs for Date 1 Inizio/Fine, Date 2 Inizio/Fine, Text filters for Clients/Zones, and a dropdown to switch between 4 or 5 different statistical views like Brand, Client, Geographical Zone, and Category).
Database:Supabase (PostgreSQL) holding a main sales table (test1) with roughly 80,000 rows of clean sales records from 2025 and 2026.
Backend / Logic: When a user clicks “Elabora Statistica”, JavaScript sends a GET request back to the n8n webhook with the query parameters. n8n fetches the data from Supabase using the Supabase Node (Get many rows) and passes it to a Code Node (JavaScript) to aggregate values, invert signs for credit notes (Note di Credito), format metrics, and generate the final dynamic HTML table.
The Pain Points & Issues Encountered:
1. Memory Limits & Bad Gateway (502 Error): When running the query with “Return All” turned ON in the Supabase node to pull the full dataset for processing inside n8n, n8n Cloud runs out of RAM and crashes with a 502 Bad Gateway. 80,000 rows are just too heavy to be processed synchronously in-memory inside an n8n workflow execution loop.
2. The “Limit” Dilemma: If I disable “Return All” and set a Limit (e.g., 3,000 rows) at the Supabase node level, it slices the first 3,000 rows before applying the client/date logic, leading to completely incomplete and wrong mathematical calculations.
3. Text & Layout Synch Bugs: When pushing complex SQL parameters or logic arrays dynamically through standard n8n expression forms (like trying to filter LIKE parameters dynamically based on optional frontend text fields), the database engine occasionally rejects the parser logic trees (PGRST100 unexpected '2' expecting isVal...).
4. Page Hanging on Fetch: When passing parameters back and forth asynchronously, the frontend spinner frequently hangs indefinitely, suggesting synchronous responses on heavy execution streams aren’t scaling.
Current Mitigation Strategy:
To fix this, we moved the heavy lifting completely to Supabase. Instead of calling the raw table, we created a PostgreSQL View (CREATE OR REPLACE VIEW vista_statistiche_aziendali) that aggregates, trims, and cleans data by Day, Brand, Zone, Category, and Client directly on the database level.
This successfully compressed the dataset, allowing n8n to safely handle the payload without throwing 502 errors.
My Questions to the Community:
Is this the recommended architecture? Is serving an interactive HTML BI/Dashboard through an n8n Webhook and relying on Code nodes for view-switching scalable for business operations, or should we look into decoupling the frontend entirely (e.g., Retool, Budibase, or a lightweight Next.js app talking directly to Supabase)?
How do you handle heavy reporting in n8n? If a user needs a deep granular breakdown (e.g., looking at a specific client down to the exact SKU/item codes over 2 years), what is the best practice to batch/paginate rows dynamically in an n8n flow without hitting gateway timeouts?
Optimizing Webhook Responses: What is the safest way to prevent frontend UI hanging/freezing during high-concurrency requests or large operations inside n8n loops?
Looking forward to your advice, architectural tips, or examples of how you solved similar reporting dashboards!
@Dedi_srl all four issues trace to one thing, youre pulling 80k rows into n8n and aggregating in the Code node, thats the RAM/502, and its why a Limit breaks the math (it slices before you aggregate). do the aggregation in the db, return only the summarised rows.
connect the Postgres node to Supabase (Host/Db/User/Port from Supabase → Project Settings → Database) and run one parameterised query, periods as params:
SELECT brand,
SUM(amount) FILTER (WHERE sale_date BETWEEN $1 AND $2) AS period1,
SUM(amount) FILTER (WHERE sale_date BETWEEN $3 AND $4) AS period2
FROM test1
WHERE ($5 = '' OR client ILIKE '%' || $5 || '%')
GROUP BY brand;
that returns a few hundred rows so nothing heavy sits in memory, the math is right (grouping runs over the whole table), and PGRST100 disappears since the filters are plain sql now, not PostgRESTs parser. the Code node then just formats that into html.
Your current approach of moving the aggregation layer into Supabase is exactly the direction I would take. n8n is excellent for orchestration and automation, but it is not designed to act as a high-volume analytics engine processing tens of thousands of rows in memory.
I have experience building scalable n8n systems with Supabase, APIs, webhooks, and AI-powered workflows. For reporting projects, I typically push heavy filtering, aggregation, and pagination into the database layer and use n8n primarily as the orchestration layer. This helps avoid memory bottlenecks, gateway timeouts, and performance issues.
I can help optimize your current architecture, improve query performance, implement efficient pagination strategies for deep drill-down reporting, and recommend the best frontend approach based on your scalability goals.
I’d be happy to discuss your current implementation and suggest practical improvements.
Your current architecture is “Prototype-grade.” It is fragile because it relies on a synchronous request-response cycle where n8n must hold the entire state of the request, the data fetch, the logic processing, and the HTML rendering in RAM.
Move to a Low-Code Frontend (Retool, Budibase, or Appsmith). These tools are designed specifically for this use case. They connect directly to Supabase (PostgreSQL), handle pagination natively, and allow you to write SQL queries that execute on the database side, returning only the final result to the UI. You can still use n8n for the “heavy lifting” (e.g., sending a weekly PDF report of this data via email), but not for the interactive UI.
Use Supabase RPC (Remote Procedure Calls) Instead of a View or a “Get Many Rows” node, create a PostgreSQL Function in Supabase.
CREATE OR REPLACE FUNCTION get_sales_stats(
start_date_1 DATE, end_date_1 DATE,
start_date_2 DATE, end_date_2 DATE,
client_filter TEXT DEFAULT NULL
)
RETURNS TABLE (category TEXT, total_sales NUMERIC, diff_percent NUMERIC) AS $$
BEGIN
RETURN QUERY
-- Your complex aggregation logic here using the parameters
-- This happens entirely inside the DB engine
END;
$$ LANGUAGE plpgsql;
How this solves your problem:
No Memory Crashes: n8n only receives the final aggregated result (e.g., 20 rows of categories) instead of 80,000 raw records.
Dynamic Filtering: You pass the dates and client names as arguments to the function. No more PGRST100 parser errors because you aren’t building complex strings in n8n expressions; you are calling a function with parameters.
Granular Data: For SKU-level breakdowns, you can add LIMIT and OFFSET parameters to your RPC function to implement true server-side pagination.
2 suggestions:
Strategy A: The Async Pattern (The “Job” Approach) If you must stay with n8n, stop trying to return the HTML in the same request.
Process: n8n continues the workflow in the background, calculates the stats, and writes the result to a report_results table in Supabase.
Poll: The frontend spinner stays active and pings a second “Check Status” webhook every 2 seconds. Once the status is “complete,” the frontend fetches the final data.
Strategy B: Direct Supabase Access If you use a tool like Retool or a Next.js app, the frontend talks to Supabase via the API. Supabase handles the concurrency and the connection pooling, which is orders of magnitude more efficient than n8n’s webhook handler.
n8n is a good fit for the second one. It is not a great place to hold an interactive analytics request open while it pulls rows, aggregates them, and renders HTML.
For the interactive part, the proof test is simple:
no n8n Code node should receive 80k raw rows;
Supabase/Postgres should return already-aggregated rows;
the browser should receive a small response, ideally hundreds of rows at most;
n8n should only orchestrate or call a stored query/RPC if you keep it in the path.
frontend calls a lightweight endpoint that returns only the grouped result;
n8n handles scheduled exports, alerts, or email/PDF delivery outside the interactive request path.
That gives you a clean rule:
If the user is waiting in the browser, keep the request small and database-shaped.
If the job is heavy, make it async or scheduled and let n8n own that workflow.