---
title: 'Barrels, Speed & Regressions'
excerpt: 'Tracing a regression and cutting tests from ~22s to under 2s.'
date: 2025-09-14
categories: appwrite, web
featureImage: '/images/barrels-and-regressions.webp'

prev:
  text: 'Appwrite: Opt-In Relations'
  link: '/blog/appwrite-opt-in-relations'

next:
  text: 'Docker: 1.75GB → 417MB'
  link: '/blog/docker-browser'
---

> [!IMPORTANT] 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**

> [!NOTE] Good to know for more context
> - We use **SvelteKit** at [**@appwrite**](https://github.com/appwrite/website). 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](https://github.com/appwrite/website/pull/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 :D.

> [!TIP] 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 -

<BlogPostImage src="/images/lucide-svelte-stats.webp" alt="Terminal output showing 6,961 icon files loaded" />

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

<BlogPostImage src="https://media1.tenor.com/m/07G0E-Fuel0AAAAC/shocked-rowan-atkinson.gif" alt="Shocked Rowan Atkinson reaction" />

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.**
