Finding and Fixing Blocking Queries

How we identified the queries blocking our initial render and shaved hundreds of milliseconds off load time.

Finding and Fixing Blocking Queries

Your app loads. Something feels slow. The profiler says nothing obvious. So you start adding console.logs everywhere like a detective dusting for fingerprints.

That was me last week with Ditto. We’d just finished a major rewrite from React to SolidJS, and the app was faster—but not as fast as it should have been. Something was blocking the initial render, and I needed to find it.

The Symptom

On hard refresh, the UI would hang for a beat before anything appeared. Not long—maybe 400-500ms—but enough to feel sluggish. The kind of delay that makes you wonder if you actually clicked the button.

First instinct: network waterfall. But the requests weren’t the problem. The problem was when they were being made and what they were blocking.

The Discovery Process

I added performance timing logs to the AuthProvider:

const authProviderStart = performance.now();
console.log(`[PERF] AuthProvider mounting...`);

auth.onAuthStateChanged((firebaseUser) => {
  console.log(
    `[PERF] onAuthStateChanged fired: ${(performance.now() - authProviderStart).toFixed(1)}ms`,
    firebaseUser ? "user present" : "no user",
  );
});

This pattern—timestamp at entry, timestamp at callback—is unglamorous but effective. It showed Firebase auth taking 20+ seconds in some cases. That seemed wrong.

Turned out Firebase was fighting with our service worker for IndexedDB access. Fixed that with requestIdleCallback to defer service worker registration. But that wasn’t the only problem.

Three Blocking Patterns We Found

1. Duplicate API Calls

Network inspection showed kg/subjects/top being called three times on load. Three components, each using their own data fetching, each hitting the same endpoint.

The fix: a shared useTopSubjects hook with TanStack Query (solid-query in our case). Now it’s one request, cached, shared across components.

Before: 3 calls to kg/subjects/top After: 1 call

2. Suspense Boundaries in the Wrong Places

SolidJS has this thing where createResource triggers Suspense by default. Great for progressive loading. Terrible when you wrap your entire sidebar in a Suspense boundary and one slow query blocks the whole thing.

We had exactly that. The sidebar’s Suspense fallback was a skeleton loader, which sounds reasonable until you realize every query inside had to resolve before anything rendered.

The fix: remove the outer Suspense boundary, let components handle their own loading states internally.

3. Resource Creation Without Initial Values

Here’s the sneaky one. Our useAuthToken hook was calling createResource on every invocation:

// Before: new resource on every call
export function useAuthToken() {
  const [token] = createResource(async () => {
    const user = auth.currentUser;
    return user ? await user.getIdToken() : null;
  });
  return token;
}

Each call created a new resource. No deduplication. And without an initialValue, each one triggered Suspense.

The fix: lift the resource into the AuthProvider, create it once, read from context everywhere:

// After: single resource, initial value prevents Suspense
const [token] = createResource(
  async () => {
    const user = auth.currentUser;
    return user ? await user.getIdToken() : null;
  },
  { initialValue: undefined },
);

That initialValue: undefined is the key. It tells SolidJS “don’t suspend, just return undefined while loading.”

The Misconception That Cost Us Time

We initially assumed we needed to disable Suspense on our data fetching. We tried adding suspense: false to createResource calls—but that option doesn’t exist in SolidJS. It was never a valid parameter.

Turns out TanStack Query (solid-query) doesn’t trigger Suspense by default anyway. You have to explicitly enable it in QueryClient config. Our actual problem was createResource calls without initialValue, which suspend by design.

Lesson: verify your assumptions about framework APIs before “fixing” things. Read the docs, not Stack Overflow.

The Result

After these changes:

  • kg/subjects/top: 3 calls reduced to 1
  • prompt/status: now non-blocking with placeholderData
  • Auth token: single resource instance, no Suspense trigger
  • Sidebar: renders progressively instead of all-or-nothing

Estimated improvement: ~500ms off initial load time. The app now feels snappy on refresh.

Practical Takeaways

For SolidJS apps:

  • Use initialValue on createResource to prevent Suspense
  • Suspense boundaries should be surgical, not sweeping
  • Lift shared resources to providers to avoid duplicate fetching

For any frontend:

  • Add timing logs before reaching for the profiler
  • Check network requests for duplicates—three identical calls is a code smell
  • Question where your loading boundaries are and what they’re blocking

The debugging pattern that works:

const start = performance.now();
console.log(`[PERF] Starting operation...`);
// ... operation ...
console.log(`[PERF] Complete: ${(performance.now() - start).toFixed(1)}ms`);

Not fancy. Very effective.


The irony of performance work is that the wins are invisible. Nobody notices when your app loads in 200ms instead of 700ms—they just don’t think about it. Which is exactly the point.


We’ve documented our SolidJS patterns as a reusable skill for Claude Code. Install it with:

npx skills add https://github.com/omniaura/skills --skill solidjs-patterns