Tuning Edge Animations in Reactflow for Optimal Performance

This post was originally written by Junki Saito, and published on our company blog. Liam ERD relies on the incredible capabilities of React Flow to visualize complex data models—like tables and relationships—on the web. We’re genuinely grateful for how React Flow enables rapid development and a beautiful user experience right out of the box. That said, when you’re working with truly massive datasets, you need to make sure your app still runs at top speed. In this post, I’ll walk you through how we tackled performance issues related to edge animations and the steps we took to keep everything running smoothly. The Challenge: Animated Edges and Performance Bottlenecks One of the standout features in React Flow is the effortless animation you can apply to edges. By simply toggling animated to true, you get a dashed-line effect that makes relationships feel alive. We loved this feature—it instantly added a sense of dynamic flow. /** * This code is written in Stackblitz * @see https://stackblitz.com/edit/sb1-74jfdcqt?file=src%2FApp.tsx */ const initialNodes = [ { id: '1', type: 'input', data: { label: 'Input Node' }, position: { x: 250, y: 25 }, }, { id: '2', data: { label: 'Default Node' }, position: { x: 100, y: 125 }, }, { id: '3', type: 'output', data: { label: 'Output Node' }, position: { x: 250, y: 250 }, }, ]; const initialEdges = [ { id: 'e1-2', source: '1', target: '2', animated: true }, { id: 'e2-3', source: '2', target: '3', animated: false }, ]; function App() { return ( ); } The issue arose when we started rendering a huge number of edges for large ERDs, like in Mastodon, which can involve over 100 tables and countless relationships. We noticed a significant dip in responsiveness whenever we hovered over or dragged around nodes—some browsers handled it better than others, but overall, it was clear that the animations were holding back performance. Identifying the Root Cause: The Culprit - stroke-dasharray After digging in, we discovered that React Flow’s edge animations rely on the CSS property stroke-dasharray. While it does create a neat dashed effect, it can seriously push your CPU when you have lots of SVG elements doing the same thing at once. Multiple user reports and bug threads confirmed that stroke-dasharray can be a major slowdown: High CPU utilisation when CSS animating stroke-dashoffset - Chromium Locked fps of animation · Issue #1003 · svgdotjs/svg.js - Github In our case, with hundreds of edges animated simultaneously, stroke-dasharray quickly became the main bottleneck. The Goal: Retain Animation, Regain Smoothness We still wanted the user experience of visually “flowing” edges. It’s a small detail that helps people see how tables and relationships link together in real time. But we absolutely had to solve the lag problem. Our Solution: A Custom Animated Edge To tackle this, we sidestepped the default animation approach in React Flow and crafted our own: Bypassing React Flow's animated props: We disabled the library’s animated prop on edges to stop using stroke-dasharray. Eliminating stroke-dasharray entirely: We cut it out for both animations and any default styling. Implementing a Custom approach: We built a CustomEdge component that animates an SVG object along the edge path, as outlined in the React Flow docs. This gave us the same visual flair—minus the performance hit. const PARTICLE_COUNT = 6 const ANIMATE_DURATION = 6 type Props = EdgeProps export const RelationshipEdge: FC = ({ data, }) => { const [edgePath] = getBezierPath({ ... }) return ( {/* data.isHighlighted will be true if the edge should be highlighted. */} {data?.isHighlighted && [...Array(PARTICLE_COUNT)].map((_, i) => ( {/* The element defines how an element moves along a motion path. */} ))} ) } By implementing these three approaches, the appearance changed from looking like Before gif image to looking like After gif image. Before After You can see exactly how we did it in our Pull Request:

Apr 8, 2025 - 06:24
 0
Tuning Edge Animations in Reactflow for Optimal Performance

This post was originally written by Junki Saito, and published on our company blog.

Liam ERD relies on the incredible capabilities of React Flow to visualize complex data models—like tables and relationships—on the web. We’re genuinely grateful for how React Flow enables rapid development and a beautiful user experience right out of the box. That said, when you’re working with truly massive datasets, you need to make sure your app still runs at top speed. In this post, I’ll walk you through how we tackled performance issues related to edge animations and the steps we took to keep everything running smoothly.

The Challenge: Animated Edges and Performance Bottlenecks

One of the standout features in React Flow is the effortless animation you can apply to edges. By simply toggling animated to true, you get a dashed-line effect that makes relationships feel alive. We loved this feature—it instantly added a sense of dynamic flow.

React Flow Demo for animated edges in React Flow with animated=true.

/**
 * This code is written in Stackblitz
 * @see https://stackblitz.com/edit/sb1-74jfdcqt?file=src%2FApp.tsx
 */
const initialNodes = [
  {
    id: '1',
    type: 'input',
    data: { label: 'Input Node' },
    position: { x: 250, y: 25 },
  },
  {
    id: '2',
    data: { label: 'Default Node' },
    position: { x: 100, y: 125 },
  },
  {
    id: '3',
    type: 'output',
    data: { label: 'Output Node' },
    position: { x: 250, y: 250 },
  },
];

const initialEdges = [
  {
    id: 'e1-2',
    source: '1',
    target: '2',
    animated: true
  },
  {
    id: 'e2-3',
    source: '2',
    target: '3',
    animated: false
  },
];

function App() {
  return (
    <ReactFlow
      nodes={initialNodes}
      edges={initialEdges}
    />
  );
}

The issue arose when we started rendering a huge number of edges for large ERDs, like in Mastodon, which can involve over 100 tables and countless relationships. We noticed a significant dip in responsiveness whenever we hovered over or dragged around nodes—some browsers handled it better than others, but overall, it was clear that the animations were holding back performance.

Identifying the Root Cause: The Culprit - stroke-dasharray

After digging in, we discovered that React Flow’s edge animations rely on the CSS property stroke-dasharray.

CSS style for animated edges in React Flow with animated=true.

While it does create a neat dashed effect, it can seriously push your CPU when you have lots of SVG elements doing the same thing at once. Multiple user reports and bug threads confirmed that stroke-dasharray can be a major slowdown:

In our case, with hundreds of edges animated simultaneously, stroke-dasharray quickly became the main bottleneck.

The Goal: Retain Animation, Regain Smoothness

We still wanted the user experience of visually “flowing” edges. It’s a small detail that helps people see how tables and relationships link together in real time. But we absolutely had to solve the lag problem.

Our Solution: A Custom Animated Edge

To tackle this, we sidestepped the default animation approach in React Flow and crafted our own:

  1. Bypassing React Flow's animated props: We disabled the library’s animated prop on edges to stop using stroke-dasharray.
  2. Eliminating stroke-dasharray entirely: We cut it out for both animations and any default styling.
  3. Implementing a Custom approach: We built a CustomEdge component that animates an SVG object along the edge path, as outlined in the React Flow docs. This gave us the same visual flair—minus the performance hit.
const PARTICLE_COUNT = 6
const ANIMATE_DURATION = 6

type Props = EdgeProps<RelationshipEdgeType>

export const RelationshipEdge: FC<Props> = ({
  data,
}) => {
  const [edgePath] = getBezierPath({
    ...
  })

  return (
    <>
      <BaseEdge
        id={id}
        path={edgePath}
      />
      {/* data.isHighlighted will be true if the edge should be highlighted. */}
      {data?.isHighlighted &&
        [...Array(PARTICLE_COUNT)].map((_, i) => (
          <ellipse
            key={`particle-${i}-${ANIMATE_DURATION}`}
            rx="5"
            ry="1.2"
            fill="url(#myGradient)"
          >
            {/* The  element defines how an element moves along a motion path.  */}
            <animateMotion
              begin={`${i * (ANIMATE_DURATION / PARTICLE_COUNT)}s`}
              dur={`${ANIMATE_DURATION}s`}
              repeatCount="indefinite"
              rotate="auto"
              path={edgePath}
              calcMode="spline"
              keySplines="0.42, 0, 0.58, 1.0"
            />
          ellipse>
        ))}
    
  )
}

By implementing these three approaches, the appearance changed from looking like Before gif image to looking like After gif image.

Before After

You can see exactly how we did it in our Pull Request: