# SolidJS and TanStack Query Workflow for New Frontend Features in Macro

> Learn Macro's 10-step SolidJS and TanStack Query workflow for building new frontend features efficiently. From setup to CI validated PRs, accelerate your development.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: how-to-guide
- Published: 2026-08-20

---

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

```bash

# 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:

```tsx
// 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:

```tsx
// 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`](https://github.com/macro-inc/macro/blob/main/apps/web/src/main.tsx) already instantiates a single `QueryClient` and wraps the application with `<QueryClientProvider>`:

```tsx
// 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:

```tsx
// 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`](https://github.com/macro-inc/macro/blob/main/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:

```bash

# Run all tests including SolidJS tests under jsdom

just test

# Or target specifically

cargo test -p web-app

```

### 8. Apply Linting and Formatting

```bash

# 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`](https://github.com/macro-inc/macro/blob/main/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`](https://github.com/macro-inc/macro/blob/main/apps/web/src/main.tsx) | Entry point with global `QueryClientProvider` setup |
| [`apps/web/src/routes/TaskRoute.tsx`](https://github.com/macro-inc/macro/blob/main/apps/web/src/routes/TaskRoute.tsx) | Production route demonstrating full query lifecycle |
| [`apps/web/src/routes/TaskRoute.test.tsx`](https://github.com/macro-inc/macro/blob/main/apps/web/src/routes/TaskRoute.test.tsx) | Test suite with `QueryClientProvider` wrapping patterns |
| [`apps/web/src/lib/service-clients/service-sync/README.md`](https://github.com/macro-inc/macro/blob/main/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`](https://github.com/macro-inc/macro/blob/main/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`](https://github.com/macro-inc/macro/blob/main/TaskRoute.tsx) and [`TaskRoute.test.tsx`](https://github.com/macro-inc/macro/blob/main/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`](https://github.com/macro-inc/macro/blob/main/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.