Skip to content

Barrels, Speed & Regressions

Published: September 14, 2025

4 min read


Barrels, Speed & Regressions

The impact

Redirect tests that once took seconds suddenly took ~22s (some 30s+).
With 180 tests, timeouts piled up and runs failed. We had to bring timings back down.


Impact

The regression first showed up in the redirect suite like this:

  • /old-link → /docs/new-link - 22.3s
  • /another-old-link → /another-new-link - 23.7s

Good to know for more context

  • We use SvelteKit at @appwrite. Redirects live in hooks.server.ts as a simple old → new path map. The logic is small so the slowness was elsewhere.
  • Our tests run against pnpm dev (the dev server), not a build preview. That surfaces dev-only costs. A full build would have hidden this, but we accept the tradeoff for faster iteration as it also helped us catch a real problem.

Investigation

Running against the dev server means that a first few requests pay a cold-start tax. To isolate that from the regression, I tested smaller slices of ≈32–64 redirects. The first ~8 requests usually just warmed things up.

  1. Rule out hooks.server.ts
    I added a quick log inside the hook which executed instantly and the 308 redirect response was immediate. The bottleneck wasn't there.

  2. Watching it live
    Using playwright test --ui, I observed the redirect status and asset loads. All were quick, but the commit event took the longest. Network profiling showed nothing unusual. This wasn't a client-side or networking issue.

  3. Bisect via tags, PRs, and CI
    In the end, I checked historical release tags, ran tests across those points, then dug into PRs and CI jobs to find the last healthy run. With help from my lead, we found PR #2282 was most likely the culprit.

You might think we could've just reverted that PR and moved on. We didn't.
It was mostly UI tweaks and a big method rename refactor. Nothing that should hit core performance.

So why did tests suddenly slow down? That why is what I wanted to understand.


Diving into Pull Request #2282

It was… a long list. 70 commits to be precise.
The next step was to jump between commits and see which range was fine and which group hid the faulty one.

Iterating through the list and running tests, I FOUND IT!
That's also the exact thing I sent to my lead once I figured out what the issue was haha 😄.

Quick debugging tip

I came across git bisect during this hunt.
It's a powerful utility that lets you bisect a range of commits by marking each as good or bad, quickly zeroing in on the commit that caused a bug or a regression.


The culprit 👹

This commit:
https://github.com/appwrite/website/pull/2282/commits/4aa0893

At first glance, it looks harmless. The changes were:

  • Adding @lucide/svelte
  • Method rename refactors from classNames to cn

I instantly discarded the second one because I'd already tested that scenario. The first one felt unlikely to cause a regression at this scale - but I judged too quickly.

Behold the issue:

typescript
import { Smile, Frown } from '@lucide/svelte';

Tiny import, massive overhead.
Given the context above, the problem should be obvious.


There's essentially no tree-shaking in pnpm dev. Importing from the package's barrel (@lucide/svelte) pulled in the entire icon set on every documentation page.

Here's what happened -

A whopping 6,961 exported icon files loaded per page under test!

Worse, the component that referenced @lucide/svelte wasn't even used - but it was exported from an index.ts barrel. Another component, FeedbackForm, was used and imported that barrel, which transitively included the unused component, which then imported the Lucide barrel.

Basically - Docs page → Feedback → unused component (re-exported) → Lucide barrel → thousands of icons per page.


Fixes

One option would have been explicit deep imports:

typescript
import Smile from '@lucide/svelte/icons/smile';
import Frown from '@lucide/svelte/icons/frown';

In our case, we removed the dependency altogether because the icons weren't needed.


Takeaways

  • Prefer deep imports (and consider lazy imports) for large packages.
  • Barrels are great for your modules; be careful re‑exporting heavy third‑party trees.
  • An unused export can still be expensive if it drags a big dependency into the dev graph.

This is an easy miss in a large codebase. Small fix, big win, and a good reminder to keep barrels lean in dev.

Darshan Pandya