SolidJS and TanStack Query Workflow for New Frontend Features in Macro

Macro's frontend team uses a 10-step workflow to build SolidJS features with TanStack Query, starting with local dev setup and ending with CI-validated pull requests.

The Macro codebase (macro-inc/macro) combines SolidJS for reactive UI components with TanStack Query (@tanstack/solid-query) for server-state management. This article walks through the complete developer workflow for integrating new features, based on patterns established in the production apps/web directory.

Project Structure and Architecture

All web UI code lives under apps/web. The architecture separates concerns across feature modules, route definitions, and service clients.

Key directories include:

  • apps/web/src/features/* — self-contained feature modules with components and logic
  • apps/web/src/routes/* — SolidJS router entry points that mount feature components
  • apps/web/src/lib/service-clients/* — HTTP clients for backend communication
  • apps/web/src/lib/queries/* — reusable query configuration and helpers

The SolidJS router (@solidjs/router) handles navigation, while TanStack Query manages caching, background refetching, and mutation invalidation.

Step-by-Step SolidJS and TanStack Query Workflow

1. Launch the Development Environment

Start Docker services and the web application using the project's justfile commands:


# Start all services without Doppler secrets management

just stack up --no-doppler

# Or run the web app directly in a Nix shell

just dev

This initializes the backend services that your TanStack Query hooks will call.

2. Scaffold the Feature Module

Create a new directory under apps/web/src/features/ for your feature. Add the main SolidJS component and any UI primitives:


apps/web/src/features/my-feature/
├── MyFeature.tsx          # Main component with query logic

├── README.md              # Documentation for props and contracts

└── components/ui/         # Feature-specific UI components

3. Create the Route Entry Point

Add a route file under apps/web/src/routes/ that imports your feature component and extracts URL parameters:

// apps/web/src/routes/MyFeatureRoute.tsx
import { Component } from 'solid-js';
import { useParams } from '@solidjs/router';
import MyFeature from '@features/my-feature/MyFeature';

const MyFeatureRoute: Component = () => {
  const params = useParams<{ projectId: string }>();
  return <MyFeature projectId={params.projectId} />;
};

export default MyFeatureRoute;

The router automatically maps this file to a URL path based on its name.

4. Implement TanStack Query Data Fetching

Import useQuery, useMutation, or useInfiniteQuery from @tanstack/solid-query. Call service clients inside the query function with proper key configuration:

// apps/web/src/features/my-feature/MyFeature.tsx
import { Component } from 'solid-js';
import { useQuery } from '@tanstack/solid-query';
import { fetchProject } from '@service-sync/client';

const MyFeature: Component<{ projectId: string }> = (props) => {
  const projectQuery = useQuery(
    () => fetchProject(props.projectId),
    { 
      queryKey: ['project', props.projectId], 
      staleTime: 5_000 
    }
  );

  return (
    <>
      {projectQuery.isLoading && <div>Loading…</div>}
      {projectQuery.error && <div>Error loading project.</div>}
      {projectQuery.data && <div>{projectQuery.data.name}</div>}
    </>
  );
};

export default MyFeature;

Service clients live in apps/web/src/lib/service-clients/ — for example, service-sync, service-connection, and service-cognition each contain typed HTTP clients for specific backend domains.

5. Leverage the Global QueryClient Provider

The root entry point at apps/web/src/main.tsx already instantiates a single QueryClient and wraps the application with <QueryClientProvider>:

// Pattern found in apps/web/src/main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/solid-query';

const queryClient = new QueryClient();

render(() => (
  <QueryClientProvider client={queryClient}>
    <Router>
      <App />
    </Router>
  </QueryClientProvider>
), root);

You do not need to create additional providers unless writing isolated tests.

6. Write Tests with QueryClientProvider

Use Vitest with SolidJS testing utilities. Always wrap components with a fresh QueryClientProvider in test files:

// apps/web/src/routes/MyFeatureRoute.test.tsx
import { render } from '@solidjs/testing-library';
import { QueryClient, QueryClientProvider } from '@tanstack/solid-query';
import MyFeatureRoute from './MyFeatureRoute';
import { vi } from 'vitest';

vi.mock('@service-sync/client', () => ({
  fetchProject: vi.fn().mockResolvedValue({ name: 'Demo Project' })
}));

test('renders project name', async () => {
  const client = new QueryClient();
  const { findByText } = render(() => (
    <QueryClientProvider client={client}>
      <MyFeatureRoute />
    </QueryClientProvider>
  ));
  
  expect(await findByText('Demo Project')).toBeInTheDocument();
});

The existing TaskRoute.test.tsx demonstrates comprehensive patterns for testing routes that depend on query data.

7. Execute the Test Suite

Run all checks from the repository root:


# Run all tests including SolidJS tests under jsdom

just test

# Or target specifically

cargo test -p web-app

8. Apply Linting and Formatting


# Rust side

cargo fmt
just clippy

# JavaScript/TypeScript side

pnpm lint

# or

npm run lint

Use just check to catch type errors before committing.

9. Document the Feature

Include a README.md in your feature directory covering:

  • Component props interface
  • TanStack Query keys and staleTime rationale
  • Backend API contracts
  • Any side effects or expected mutation invalidations

10. Submit for Review

Push your branch and open a Pull Request. CI runs just test and validates that your SolidJS and TanStack Query integration follows established patterns. Reviewers verify proper query usage, test coverage, and adherence to UI guidelines.

TanStack Query Integration Patterns

Global QueryClient — Created once at application startup and shared across all components via context.

Query Hooks — Return reactive signals for data, error, isLoading, and refetch. Access cached data instantly or trigger background refetching based on staleTime.

Mutations with Invalidation — After successful writes, call queryClient.invalidateQueries(['key', params]) to automatically refetch dependent data.

Configurable Caching — Default policies are set in apps/web/src/lib/queries/* modules. Override per-query with options like staleTime, cacheTime, or refetchOnWindowFocus.

Key Reference Files

File Purpose
apps/web/src/main.tsx Entry point with global QueryClientProvider setup
apps/web/src/routes/TaskRoute.tsx Production route demonstrating full query lifecycle
apps/web/src/routes/TaskRoute.test.tsx Test suite with QueryClientProvider wrapping patterns
apps/web/src/lib/service-clients/service-sync/README.md HTTP client documentation for query functions
apps/web/src/lib/queries/* Reusable query configuration helpers

Summary

  • SolidJS and TanStack Query workflow in Macro follows 10 structured steps from environment setup to PR submission
  • Feature modules live in apps/web/src/features/ with self-contained components and documentation
  • Route files in apps/web/src/routes/ bridge URL parameters to feature components using SolidJS router
  • TanStack Query hooks call service clients from apps/web/src/lib/service-clients/ with explicit query keys and staleTime configuration
  • Global QueryClient is instantiated in apps/web/src/main.tsx and provided via <QueryClientProvider>
  • Tests require manual provider wrapping with a fresh QueryClient to match runtime behavior
  • Reference implementations in TaskRoute.tsx and TaskRoute.test.tsx demonstrate production-ready patterns

Frequently Asked Questions

How do I configure query caching behavior for a specific feature?

Pass cache options directly to useQuery or define reusable defaults in apps/web/src/lib/queries/. Common overrides include staleTime (milliseconds until data is considered stale), cacheTime (how long to keep unused data), and refetchOnWindowFocus. For project-wide consistency, prefer extending shared query helpers rather than duplicating configuration.

What testing utilities work best with SolidJS and TanStack Query?

Macro uses Vitest with @solidjs/testing-library for component tests. Always render components with a dedicated QueryClientProvider to prevent test pollution. Mock service clients with vi.fn() from Vitest, and use findBy* queries from testing-library to wait for async query resolution. See TaskRoute.test.tsx for the canonical pattern.

Where should I place HTTP client logic that my queries call?

Service clients belong in apps/web/src/lib/service-clients/. The service-sync, service-connection, and service-cognition packages contain typed clients for specific backend services. Import these directly into your query functions — do not inline fetch logic inside components. Each service client directory includes a README documenting available endpoints and expected request/response shapes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →