npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

next-unified-query

v0.2.0

Published

React hooks and components for next-unified-query-core

Readme

Next Unified Query 🚀

npm version npm downloads publish size TypeScript MIT License

The Modern HTTP Client for React - Unified Config, Type-Safe, Performance Optimized

Combines the best of TanStack Query and fetch with unmatched TypeScript support and performance optimizations

📚 Documentation

🚀 Quick Start📖 API Reference🎓 User GuidePerformance💬 GitHub


Why Next Unified Query?

Stop fighting with scattered configurations, endless re-renders, and type safety issues. Next Unified Query is built for modern React applications that demand performance, type safety, and developer experience.

🔥 Problems We Solve

| Common Pain Points | Next Unified Query Solution | |---|---| | 🔄 Unnecessary re-renders hurting performance | Optimized re-rendering with selective subscriptions | | 🔧 Scattered baseURL configs across app | Unified configuration - set once, works everywhere | | 🐛 Runtime errors from wrong HTTP methods | Compile-time safety with method-specific types | | 📦 Large bundle sizes impacting load times | ~26KB gzipped - optimized and tree-shakeable | | 🌐 Complex SSR setup and hydration issues | First-class Next.js support with zero config | | 🤯 Verbose boilerplate for simple requests | Global functions for direct API calls |

💡 Unique Advantages

  • 🎯 Set It Once, Use Everywhere: Configure baseURL, headers, and interceptors once - they work across useQuery, useMutation, and global functions
  • 🛡️ Compile-Time HTTP Safety: useQuery only allows GET/HEAD, useMutation prevents GET - catch errors before runtime
  • ⚡ Performance by Default: Optimized re-rendering that only updates when data you actually use changes
  • 🔧 Factory Patterns: Define type-safe, reusable API definitions with full TypeScript inference
  • 🌐 SSR-First: Built for Next.js with seamless server-side rendering and hydration
  • 🆕 Environment-Specific Interceptors: Separate client/server interceptors without typeof window checks
  • 🎭 React 18+ Features (v0.2.0+): Built-in Error Boundary, Suspense support, and global default options

🚀 Quick Start (30 seconds to running)

Installation

npm install next-unified-query
# or
yarn add next-unified-query
# or
pnpm add next-unified-query

✨ Includes popular libraries built-in:

  • Zod v4 for schema validation (no separate install needed!)
  • es-toolkit for high-performance utility functions
  • quick-lru for optimized caching

📦 Package Size: ~119KB publish size. Install size is larger (~6.6MB) because we include Zod v4 for type-safe validation out of the box. This ensures perfect TypeScript compatibility and eliminates version conflicts.

Basic Setup

// app/query-config.ts - Shared configuration
import type { QueryClientOptions } from 'next-unified-query';

export const queryConfig: QueryClientOptions = {
  baseURL: 'https://jsonplaceholder.typicode.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
  // 🆕 Environment-specific interceptors (v0.2.0+)
  interceptors: {
    // Runs in all environments
    request: (config) => {
      config.headers['X-App-Version'] = '1.0.0';
      return config;
    }
  },
  clientInterceptors: {
    // Client-only (browser) - direct access to localStorage, window, etc.
    request: (config) => {
      const token = localStorage.getItem('token'); // No typeof check needed!
      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    },
    error: (error) => {
      if (error.response?.status === 401) {
        localStorage.removeItem('token');
        window.location.href = '/login'; // Direct window access!
      }
      return Promise.reject(error);
    }
  },
  serverInterceptors: {
    // Server-only (Node.js) - access to process.env, server-side logging
    request: (config) => {
      config.headers['X-Server-Region'] = process.env.REGION;
      return config;
    }
  }
};
// app/layout.tsx - Configure for SSR
import { configureQueryClient } from 'next-unified-query';
import { queryConfig } from './query-config';
import { Providers } from './providers';

// Configure for both SSR and client
configureQueryClient(queryConfig);

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
// app/providers.tsx - Client Component
'use client';
import { QueryClientProvider } from 'next-unified-query/react';
import { queryConfig } from './query-config';

export function Providers({ children }) {
  return (
    <QueryClientProvider config={queryConfig}>
      {children}
    </QueryClientProvider>
  );
}

Your First Query (Now baseURL works everywhere!)

// app/users/page.tsx
import { useQuery, useMutation } from 'next-unified-query/react';
import { get, post } from 'next-unified-query';

export default function UsersPage() {
  // ✅ All use the same baseURL automatically
  const { data, isLoading } = useQuery({
    cacheKey: ['users'],
    url: '/users'  // → https://jsonplaceholder.typicode.com/users
  });

  const createUser = useMutation({
    url: '/users',    // → https://jsonplaceholder.typicode.com/users
    method: 'POST'
  });

  // ✅ Even global functions use the same config
  const handleExport = async () => {
    const csv = await get('/users/export');  // → same baseURL!
  };

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      <h1>Users ({data?.length})</h1>
      {data?.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
      <button onClick={() => createUser.mutate({ name: 'New User' })}>
        Add User
      </button>
    </div>
  );
}

🎉 That's it! One configuration, works everywhere. No more scattered baseURL configs!


🌟 Key Features That Set Us Apart

🔧 Unified Configuration System

Configure once, use everywhere - the way it should be

// ✅ Next Unified Query - ONE configuration in Provider
<QueryClientProvider config={{
  baseURL: 'https://api.example.com',
  headers: { 'Authorization': 'Bearer token' },
  timeout: 10000,
  interceptors: {
    request: (config) => {
      config.headers['Authorization'] = getToken();
      return config;
    }
  }
}}>
  {/* Now ALL these work with the same config: */}
  {/* useQuery({ url: '/users' })        ✅ Auto baseURL */}
  {/* useMutation({ url: '/posts' })     ✅ Auto baseURL */}
  {/* await post('/analytics', data)     ✅ Auto baseURL */}
</QueryClientProvider>

Traditional approaches often require:

  • Multiple configuration files and instances
  • Separate HTTP client setup
  • Manual coordination between different libraries
  • Complex integration and maintenance

🛡️ Compile-Time HTTP Method Safety

Catch API mistakes before they hit production

// ✅ Type-safe by design
const { data } = useQuery({
  cacheKey: ['users'],
  url: '/users'  // ✅ Only GET/HEAD allowed - perfect for data fetching
});

const createUser = useMutation({
  url: '/users',
  method: 'POST'  // ✅ POST/PUT/DELETE/PATCH allowed - perfect for mutations
});

// ❌ This won't even compile!
const badQuery = useQuery({
  url: '/users',
  method: 'POST'  // 🚨 TypeScript Error: useQuery doesn't allow POST
});

Why this matters: Prevents accidental cache pollution and clarifies intent.

🏭 Factory Pattern for Scalable APIs

Type-safe, reusable API definitions that scale with your team

// ✨ Import Zod directly - no separate installation needed!
import { createQueryFactory, createMutationFactory, z } from 'next-unified-query';

// Define once, use everywhere with full type safety
const userQueries = createQueryFactory({
  list: {
    cacheKey: () => ['users'] as const,
    url: () => '/users',
    schema: z.array(userSchema) // Automatic TypeScript inference! 
  },
  get: {
    cacheKey: (id: number) => ['users', id] as const,
    url: (id: number) => `/users/${id}`,
    schema: userSchema
  }
});

const userMutations = createMutationFactory({
  create: {
    url: () => '/users',
    method: 'POST',
    requestSchema: createUserSchema,
    responseSchema: userSchema
  }
});

// Use with perfect TypeScript support
const { data } = useQuery(userQueries.list);        // data is User[] ✨
const { data: user } = useQuery(userQueries.get, { params: { id: 1 } }); // user is User ✨
const createMutation = useMutation(userMutations.create);

Advanced Performance Optimizations

Built on top of query library best practices with additional enhancements

// ✅ Selective subscriptions for optimal performance
function UserProfile({ userId }) {
  const { data: userName } = useQuery({
    cacheKey: ['user', userId],
    url: `/users/${userId}`,
    select: (user) => user.name  // ✨ Only re-render on name changes
  });

  return <h1>{userName}</h1>;
}

// ✅ PLUS: Unified configuration benefits
// - No need to manage multiple HTTP client instances
// - Automatic baseURL application reduces config errors
// - Type-safe HTTP methods prevent cache pollution
// - Global functions share the same optimized setup

// Example: All these benefit from the same performance optimizations
const { data } = useQuery({ url: '/users' });           // Optimized rendering
const mutation = useMutation({ url: '/users' });        // Prevents GET usage
const response = await get('/users');                   // Same interceptors

🌐 First-Class SSR Support

Zero-config server-side rendering that just works

// app/users/[id]/page.tsx - Next.js App Router
import { ssrPrefetch } from 'next-unified-query';
import { HydrationBoundary } from 'next-unified-query/react';
import { userQueries } from '@/lib/queries';

export default async function UserPage({ params }) {
  // ✅ Server-side prefetching uses config from configureQueryClient()
  // No need to pass config - it's already configured globally!
  const dehydratedState = await ssrPrefetch([
    [userQueries.get, { id: params.id }],
    [userQueries.posts, { userId: params.id }]
  ]);

  return (
    <HydrationBoundary state={dehydratedState}>
      <UserDetail userId={params.id} />
    </HydrationBoundary>
  );
}

function UserDetail({ userId }) {
  // ✅ Uses prefetched data immediately, no loading state!
  const { data } = useQuery(userQueries.get, { params: { id: userId } });
  
  return <div>{data?.name}</div>; // Instant render! ⚡
}

🛡️ Built-in Error Boundary Support (v0.2.0+)

Graceful error handling with zero configuration

// ✅ Wrap your app with Error Boundary
import { QueryErrorBoundary } from 'next-unified-query/react';

function App() {
  return (
    <QueryErrorBoundary
      fallback={(error, reset) => (
        <div>
          <h2>Something went wrong!</h2>
          <pre>{error.message}</pre>
          <button onClick={reset}>Try again</button>
        </div>
      )}
      onError={(error, errorInfo) => {
        // Log to error reporting service
        console.error('Error caught:', error, errorInfo);
      }}
    >
      <YourApp />
    </QueryErrorBoundary>
  );
}

// ✅ Use with hooks - errors automatically bubble up
function UserProfile() {
  const { data } = useQuery({
    url: '/users/1',
    throwOnError: true  // Throw to Error Boundary on error
  });
  
  const mutation = useMutation({
    url: '/users',
    method: 'POST',
    throwOnError: (error) => error.response?.status >= 500  // Conditional throwing
  });
  
  return <div>{data?.name}</div>;
}

// ⚠️ Important: When using throwOnError: true, make sure your component is wrapped
// with an Error Boundary. Without it, errors will crash your application.
// In development mode, you'll see a warning in the console if an Error Boundary is missing.

🎭 React Suspense Integration (v0.2.0+)

Modern loading states with concurrent rendering support

// ✅ Enable Suspense mode for declarative loading states
import { Suspense } from 'react';

function UserList() {
  const { data } = useQuery({
    url: '/users',
    suspense: true  // Enable Suspense mode
  });
  
  // No loading state needed - Suspense handles it!
  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

// Wrap with Suspense boundary
function App() {
  return (
    <Suspense fallback={<Loading />}>
      <UserList />
    </Suspense>
  );
}

// ⚠️ Important: When using suspense: true, make sure your component is wrapped 
// with <Suspense>. Without it, your app may crash when the component suspends.
// In development mode, you'll see a warning in the console if a Suspense boundary is missing.

// ✅ Combine with Error Boundary for complete async handling
<QueryErrorBoundary fallback={ErrorFallback}>
  <Suspense fallback={<Loading />}>
    <YourApp />
  </Suspense>
</QueryErrorBoundary>

🎛️ Global Default Options (v0.2.0+)

Configure once, apply everywhere with intelligent merging

// ✅ Set default behavior for all queries and mutations
<QueryClientProvider config={{
  baseURL: 'https://api.example.com',
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000,      // 5 minutes
      gcTime: 10 * 60 * 1000,         // 10 minutes
      throwOnError: false,            // Don't throw by default
      suspense: false                 // Suspense disabled by default
    },
    mutations: {
      throwOnError: (error) => error.response?.status >= 500  // Only throw on server errors
    }
  }
}}>
  {children}
</QueryClientProvider>

// Individual queries can override defaults
const { data } = useQuery({
  url: '/important-data',
  suspense: true,         // Override: enable Suspense for this query
  staleTime: 0            // Override: always fresh
});

🔄 Global Functions for Direct API Calls

When you need direct API access without React hooks

// ✅ Perfect for event handlers, utilities, and server functions
async function exportUserData() {
  try {
    const users = await get('/users');           // Same config as hooks!
    const csv = await post('/export', {          // Same interceptors!
      data: users.data,
      format: 'csv'
    });
    
    downloadFile(csv.data);
    
    // Analytics tracking
    await post('/analytics', { 
      action: 'export_users',
      count: users.data.length 
    });
  } catch (error) {
    toast.error('Export failed');
  }
}

// ✅ Server actions (Next.js App Router)
async function createUserAction(formData: FormData) {
  'use server';
  
  const user = await post('/users', {
    name: formData.get('name'),
    email: formData.get('email')
  });
  
  revalidateTag('users');
  return user.data;
}

📊 Performance Metrics

Library Performance & Features

Next Unified Query offers:

  • Bundle Size: ~26KB gzipped (complete solution)
  • E2E Performance: 142ms total processing time
  • Cache Performance: 47.3x improvement with optimized caching
  • Memory Usage: <5MB efficient memory management
  • TypeScript: Full type safety with compile-time method validation
  • Configuration: Single unified setup for all request methods

🚀 Performance Highlights

Real-world performance metrics from controlled E2E testing:

  • 🏆 Total Processing Speed: 142ms average response time
  • ⚡ Cache Performance: 93x improvement (280ms → 3ms) with 100% hit rate
  • 🌐 Network Performance: Optimized for mobile networks (336ms on 3G)
  • 📦 Bundle Efficiency: Complete solution at 26KB gzipped
  • 🧠 Memory Excellence: <5MB usage with efficient garbage collection

🎯 When to Use Next Unified Query

Ideal for projects that need:

  • 🚀 High performance data fetching
  • 📱 Mobile-optimized applications
  • 🛡️ Compile-time type safety for HTTP methods
  • 🔧 Unified configuration management
  • 🌐 Server-side rendering support
  • 📦 Complete solution without additional HTTP client setup

📊 View Complete Library Comparison →

Real-World Benefits

// 🎯 The unified approach eliminates common pain points:

// ✅ Next Unified Query: One config in Provider, works everywhere
<QueryClientProvider config={{
  baseURL: 'https://api.example.com',
  headers: { 'Authorization': 'Bearer token' },
  interceptors: { /* ... */ }
}}>
  {/* All methods share the same setup automatically */}
</QueryClientProvider>

// Now ALL methods share the same setup:
const { data } = useQuery({ url: '/users' });      // ✅ Auto baseURL
const result = await post('/users', userData);     // ✅ Same config
const mutation = useMutation({ url: '/posts' });   // ✅ Type-safe

// Traditional approach: Multiple configurations to manage
const queryClient = new QueryClient(queryConfig);
const httpClient = createHttpClient(httpConfig);
const fetchWrapper = createFetch(fetchConfig);
// Multiple configurations require careful coordination

Developer Experience Metrics

Developer Experience Metrics:

  • Setup Lines of Code: 8 lines for complete configuration
  • TypeScript Errors Caught: 95% compile-time validation
  • Config Duplication: Zero - single source of truth
  • Learning Curve: 1-2 hours to productive development

🎯 Enterprise-Ready Features

  • 🔍 Built-in Monitoring: Real-time performance tracking with getStats()
  • 🛡️ Memory Protection: Automatic cleanup and leak prevention
  • ⚙️ Production Config: Retry logic, timeouts, and error handling
  • 📊 Quality Assurance: 7 comprehensive E2E tests with real browser testing

🔧 Production Setup Guide →


🛠️ Ecosystem & Framework Support

Officially Supported

  • Next.js (App Router + Pages Router)
  • Vite + React
  • Create React App
  • Remix (experimental)

🔧 Built-in Integrations

  • TypeScript: First-class support with full type inference
  • Zod: Schema validation for runtime type safety
  • React DevTools: Built-in query debugging
  • ESLint: Custom rules for best practices

🚀 Quick Links

📚 Documentation

💬 Community & Support


💡 Quick Decision Guide

Choose Next Unified Query if you want:

  • Unified configuration across all request methods
  • Compile-time safety for HTTP methods
  • Minimal re-renders and maximum performance
  • First-class TypeScript experience
  • Simple Next.js SSR without the complexity

🤔 Consider Your Project Needs

  • Evaluate your specific performance requirements
  • Consider your team's familiarity with different approaches
  • Assess your current architecture and migration effort

📄 License

MIT © newExpand


Made with ❤️ for the React community

Star us on GitHub📖 API Docs🎓 User GuidePerformance