We were building a knowledge graph view — a React Flow canvas that shows topic chains and semantic links between video segments. Nodes rendered fine. But every time we loaded the view, the edges (the connecting lines between nodes) were simply gone.
No errors in the console. Nodes in place. Just... no edges.
Here's the two-bug chain that caused it.
Bug 1: overflow-hidden creates a stacking context that clips SVG edges
The first culprit was this wrapper div:
// GraphView.tsx — BEFORE
<div className="rounded-xl overflow-hidden border border-gray-800" style={{ height: 420 }}>
<VideoKnowledgeGraph ... />
</div>
overflow-hidden in CSS does two things most people don't think about:
- It clips content that overflows the box (the obvious thing)
- It creates a new stacking context
ReactFlow renders its edges as an SVG layer that is absolutely positioned within the canvas container. When an ancestor has overflow: hidden, that SVG layer gets clipped — even if it's technically within the visible bounds — because the stacking context changes how the browser composites layers.
The fix: use overflow: clip instead.
// GraphView.tsx — AFTER
{/* overflow-clip instead of overflow-hidden: clips visually without creating a new
stacking context that would cause ReactFlow's SVG edge layer to be invisible */}
<div className="rounded-xl border border-gray-800" style={{ height: 420, overflow: "clip" }}>
<VideoKnowledgeGraph ... />
</div>
overflow: clip behaves like overflow: hidden visually — content is clipped at the box boundary — but crucially it does not create a new stacking context. The SVG edge layer can now composite correctly.
This is one of those CSS properties that exists specifically because overflow: hidden has an unfortunate side effect that trips up complex rendering scenarios like SVG overlays, fixed-positioned children, and canvas-based UIs.
Bug 2: Next.js dynamic() imports need an explicit ReactFlowProvider
Even after fixing the overflow issue, edges were still missing in some cases. The second bug was more subtle.
ReactFlow uses React context internally. The <ReactFlow> component expects a ReactFlowProvider to be present somewhere up the tree. In most setups, you either wrap your app or page in a provider, or rely on React Flow's own internal provider being initialized before the component renders.
The problem: we were loading the graph component with Next.js dynamic():
// project/page.tsx
const VideoKnowledgeGraph = dynamic(
() => import('@/components/VideoKnowledgeGraph'),
{ ssr: false }
);
With dynamic(), the component is loaded in a separate chunk and initialized asynchronously. The issue is that when this happens, the ReactFlow context — specifically the provider that manages the internal store for nodes, edges, and the SVG renderer — can be absent or not yet initialized at the time the SVG edge layer tries to render.
The result: nodes render (they don't depend on the internal edge store in the same way), but edges don't.
Fix: explicitly wrap the dynamically imported component in <ReactFlowProvider>:
// VideoKnowledgeGraph.tsx — AFTER
import { ReactFlow, ReactFlowProvider, ... } from '@xyflow/react';
export default function VideoKnowledgeGraph({ graph, ... }) {
// ...
return (
// ReactFlowProvider is required here because this component is loaded via Next.js
// dynamic() import. Without an explicit provider, the ReactFlow context (including
// the SVG edge renderer) can be missing, causing edges to not render.
<ReactFlowProvider>
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column' }}>
{/* toolbar */}
<div style={{ width: '100%', height: '100%', position: 'relative', flex: 1, minHeight: 0 }}>
<ReactFlow
nodes={nodesWithSelection}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
nodeTypes={nodeTypes}
fitView
fitViewOptions={{ padding: 0.2 }}
minZoom={0.2}
maxZoom={2}
>
<Background />
<Controls />
</ReactFlow>
</div>
</div>
</ReactFlowProvider>
);
}
The rule of thumb: any ReactFlow component loaded via dynamic() should include its own ReactFlowProvider. Don't assume the context will be present from a parent — with async chunk loading, the initialization order isn't guaranteed.
Why both bugs were needed to hide edges
Either bug alone might have been survivable:
- Just the stacking context issue → edges might render in some browsers but get clipped in others
- Just the missing provider → React Flow might fall back to an ancestor provider if one existed up the tree
Together, they compounded: the SVG layer wasn't rendering at all (provider issue), and even if it had rendered, it would have been clipped (overflow issue).
When debugging React Flow edge visibility, check these two things first:
-
Any ancestor with
overflow: hidden? Switch tooverflow: cliporoverflow: visible. -
Is the component loaded dynamically? Wrap it in
<ReactFlowProvider>.
Takeaway
overflow: hidden is one of CSS's "does more than it says" properties — it's worth knowing that it triggers stacking context creation. And when using React Flow with Next.js dynamic(), always provide the context explicitly rather than hoping it exists up the tree.
Both are easy one-line fixes once you know what to look for. The hard part is knowing what to look for.
Top comments (0)