Skip to main content

SWR Next.js: Architecting Resilient Data Fetching for Scalable Applications

NR Tech Studio Team
NR Tech Studio
40 min read

A recent study by Google found that a one-second delay in mobile page load can impact conversion rates by up to 20%. In modern web development, particularly with frameworks like Next.js, optimizing data fetching is paramount for user experience and application performance. SWR Next.js refers to the integration of the SWR (Stale-While-Revalidate) data fetching library within Next.js applications, providing an efficient strategy for managing client-side data, caching, and revalidation. This combination significantly enhances perceived performance, reduces server load, and improves the overall responsiveness of web applications, critical for maintaining high availability and user satisfaction in production environments.

From a cloud architect’s perspective, the synergistic relationship between SWR and Next.js offers a robust pattern for building highly performant and scalable web services. Next.js excels in server-side rendering (SSR) and static site generation (SSG), laying a strong foundation for initial page loads and SEO. SWR then takes over for client-side data management, intelligently handling subsequent data requests, caching, and real-time updates. This division of concerns ensures optimal resource utilization across the client and server, reducing latency and enhancing the reliability of data delivery, which is fundamental for applications operating at scale.

Understanding the underlying mechanics of SWR, its integration patterns with Next.js, and its implications for infrastructure stability is essential. This article will explore how SWR’s intelligent caching, revalidation strategies, and error handling mechanisms contribute to building resilient, high-performance applications capable of handling significant user loads and complex data requirements. We will delve into architectural considerations, performance benchmarks, and best practices for deploying SWR-powered Next.js applications in enterprise-grade cloud environments.

Understanding SWR and its Role in Modern Web Architecture

SWR, which stands for Stale-While-Revalidate, is a powerful data fetching hook library for React, and by extension, Next.js. Its core philosophy is derived from an HTTP cache invalidation strategy popularized by RFC 5861. In simple terms, SWR first returns the data from cache (stale data), then sends the fetch request (revalidate), and finally updates the data with the freshest version once it arrives. This approach significantly improves the user experience by providing immediate feedback, reducing perceived loading times, and ensuring data consistency without blocking the UI.

From an architectural standpoint, SWR acts as an intelligent client-side data layer that complements the server-side rendering capabilities of Next.js. Next.js, with its SSR, SSG, and Incremental Static Regeneration (ISR) features, provides excellent initial page load performance and SEO benefits by delivering pre-rendered HTML. However, once the page is hydrated on the client, subsequent data interactions typically rely on client-side fetching. This is where SWR shines. Instead of traditional `useEffect` based fetching with manual state management, SWR abstracts away much of the complexity, offering built-in features like caching, revalidation on focus, revalidation on network recovery, and request deduplication.

Consider a large-scale e-commerce platform built with Next.js. When a user navigates to a product page, Next.js might pre-render the initial product details using `getServerSideProps` or `getStaticProps`. However, if the user then interacts with a filter, sorts items, or navigates through pagination, these actions typically trigger new data requests. Without SWR, each of these interactions would likely involve a loading spinner, a delay while the new data fetches, and manual cache management to prevent redundant requests. SWR mitigates this by immediately showing the previously fetched data (if available), fetching the new data in the background, and seamlessly updating the UI once the fresh data arrives. This ‘instant-on’ experience is crucial for user engagement and retention, directly impacting business metrics like conversion rates and session duration.

Furthermore, SWR’s request deduplication mechanism is a critical feature for infrastructure efficiency. In complex applications, multiple components might attempt to fetch the same data concurrently. SWR automatically de-duplicates these requests, ensuring that only one network call is made for a given key within a short time window. This reduces unnecessary network traffic, lessens the load on backend APIs, and conserves client-side resources. For cloud architects managing large-scale deployments, reducing redundant API calls translates directly into lower operational costs for data transfer and API gateway usage, while also improving the responsiveness of backend services by minimizing spurious load spikes. The library’s ability to automatically revalidate data when the window regains focus or when the network connection is restored also contributes to a highly resilient application, ensuring users always see reasonably fresh data without explicit manual refreshes.

Integrating SWR with Next.js for Optimal Data Fetching

Integrating SWR into a Next.js application is straightforward, yet it unlocks significant improvements in data fetching patterns. The primary mechanism for using SWR is the useSWR hook, which accepts a unique key (typically the API endpoint URL) and a fetcher function. The fetcher is responsible for making the actual data request, often using a standard library like fetch or axios.

// utils/fetcher.ts
export const fetcher = async (url: string) => {
  const res = await fetch(url);
  if (!res.ok) {
    const error = new Error('An error occurred while fetching the data.');
    // Attach extra info to the error object.
    (error as any).info = await res.json();
    (error as any).status = res.status;
    throw error;
  }
  return res.json();
};

// components/UserProfile.tsx
import useSWR from 'swr';
import { fetcher } from '../utils/fetcher';

interface User {
  id: string;
  name: string;
  email: string;
  // ... other user properties
}

export default function UserProfile({ userId }: { userId: string }) {
  const { data, error, isLoading } = useSWR<User>(`/api/users/${userId}`, fetcher);

  if (error) {
    console.error("Failed to load user data:", error);
    return <div>Failed to load user.</div>;
  }
  if (isLoading) return <div>Loading user profile...</div>;
  if (!data) return <div>No user data found.</div>;

  return (
    <div>
      <h2>{data.name}</h2>
      <p>Email: {data.email}</p>
    </div>
  );
}

This basic integration demonstrates SWR’s core functionality: managing loading states, error handling, and data retrieval. For Next.js, SWR’s client-side caching mechanism complements the framework’s server-side data fetching strategies. When a page is initially rendered using getServerSideProps or getStaticProps, the data is fetched on the server and embedded into the HTML. This provides a fast initial load. SWR can then be pre-populated with this initial data using the fallbackData option, which ensures that the client-side SWR cache is immediately primed, preventing a re-fetch on component mount and providing an even smoother transition.

// pages/users/[id].tsx
import useSWR from 'swr';
import { fetcher } from '../../utils/fetcher';
import { GetServerSideProps } from 'next';

interface User {
  id: string;
  name: string;
  email: string;
}

interface UserPageProps {
  user: User;
}

export default function UserPage({ user: initialUser }: UserPageProps) {
  // SWR will use initialUser as fallbackData, then revalidate in the background.
  const { data: user, error, isLoading } = useSWR<User>(`/api/users/${initialUser.id}`, fetcher, {
    fallbackData: initialUser,
    revalidateOnMount: true, // Revalidate on mount to get the freshest data
  });

  if (error) return <div>Failed to load user.</div>;
  if (isLoading) return <div>Loading...</div>; // This will only show if fallbackData is not provided or revalidation is pending

  return (
    <div>
      <h1>User Profile</h1>
      <h2>{user?.name}</h2>
      <p>Email: {user?.email}</p>
    </div>
  );
}

export const getServerSideProps: GetServerSideProps = async (context) => {
  const { id } = context.params as { id: string };
  const initialUser = await fetcher(`http://localhost:3000/api/users/${id}`);
  return {
    props: {
      user: initialUser,
    },
  };
};

This pattern ensures that the user immediately sees the server-rendered content, and SWR then takes responsibility for keeping that data fresh. The revalidateOnMount: true option ensures that SWR will perform a revalidation request in the background after the component mounts, updating the UI with any newer data if available. This provides an excellent balance between initial load speed and client-side data freshness. For infrastructure management, this approach offloads continuous data fetching from the server to the client after the initial render, reducing the number of full page renders and associated server compute cycles. It also allows for more flexible caching strategies at the CDN level for static assets, while dynamic data is efficiently managed by SWR.

Advanced SWR Features for Enterprise-Grade Applications

For enterprise-grade applications, SWR extends beyond basic data fetching to offer a suite of advanced features that address complex data requirements and enhance application resilience and user interactivity. These features are crucial for systems demanding high availability, real-time feedback, and efficient resource utilization.

Dependent Fetching

Many applications require data to be fetched sequentially, where the result of one API call is a prerequisite for another. SWR handles this gracefully through conditional fetching. By passing a falsy key, SWR will not trigger a request. This is particularly useful when dealing with user-specific data that only becomes available after authentication, or when fetching details based on a parent resource ID.

import useSWR from 'swr';
import { fetcher } from '../utils/fetcher';

interface UserProfile {
  id: string;
  name: string;
}

interface UserOrders {
  orderId: string;
  amount: number;
}

export default function UserDashboard({ userId }: { userId: string }) {
  const { data: profile } = useSWR<UserProfile>(`/api/profile/${userId}`, fetcher);

  // Only fetch orders if profile data is available
  const { data: orders } = useSWR<UserOrders[]>(
    profile ? `/api/orders/${profile.id}` : null, 
    fetcher
  );

  if (!profile) return <div>Loading profile...</div>;
  if (!orders) return <div>Loading orders...</div>;

  return (
    <div>
      <h2>Welcome, {profile.name}</h2>
      <h3>Your Orders:</h3>
      <ul>
        {orders.map((order) => (
          <li key={order.orderId}>Order {order.orderId}: ${order.amount}</li>
        ))}
      </ul>
    </div>
  );
}

Optimistic UI Updates

Providing instant feedback to users, even before a server response is received, is a cornerstone of modern UX. SWR’s mutate function enables optimistic UI updates. When a user performs an action (e.g., liking a post, adding an item to a cart), the UI can immediately reflect the expected change. SWR then sends the actual request to the server in the background. If the server request succeeds, the UI remains updated. If it fails, SWR automatically reverts the UI to the previous state, ensuring data integrity. This pattern significantly reduces perceived latency, especially over high-latency networks, and is essential for highly interactive applications.

Pagination and Infinite Loading

Handling large datasets efficiently requires sophisticated pagination or infinite scrolling mechanisms. SWR provides the useSWRInfinite hook, which simplifies managing multiple pages of data. It abstracts the logic for appending new data, managing page indexes, and handling loading states for continuous data streams. This is critical for applications like social media feeds, large dashboards, or product listings, where users expect to browse extensive content without performance degradation.

Real-time Updates with SWR

While SWR is primarily a client-side data fetching library, it can be extended to support real-time updates. By integrating with WebSockets or Server-Sent Events (SSE), SWR can be programmatically told to revalidate specific keys when a real-time event occurs. For instance, if a new message arrives in a chat application, the WebSocket listener can trigger mutate('/api/messages'), prompting SWR to fetch the latest messages and update the UI. This hybrid approach allows applications to benefit from SWR’s efficient caching for static data while maintaining real-time responsiveness for dynamic content, crucial for collaborative tools and monitoring dashboards.

These advanced features empower developers to build highly responsive, data-consistent, and performant applications that meet the rigorous demands of enterprise environments. By leveraging SWR’s capabilities, architects can design systems that provide superior user experiences while optimizing resource usage across the entire stack.

SWR’s Caching Mechanisms and Cache Invalidation Strategies

SWR’s power lies in its intelligent caching and revalidation mechanisms, which are fundamental to its ‘Stale-While-Revalidate’ strategy. Understanding these mechanisms is crucial for optimizing data flow, ensuring data consistency, and designing robust, high-performance applications. At its core, SWR maintains an in-memory cache for fetched data. When useSWR is called with a specific key, it first checks this cache. If data exists, it’s returned immediately (the ‘stale’ part), providing an instant UI update. Concurrently, SWR initiates a background network request to re-fetch the data (the ‘revalidate’ part). Once the new data arrives, the cache is updated, and the component re-renders with the fresh data.

Default Revalidation Strategies

SWR comes with several built-in revalidation triggers that automatically ensure data freshness:

  1. Revalidate on Focus: When a user refocuses a window or switches tabs, SWR automatically revalidates the data associated with the active components. This ensures that users always see up-to-date information when they return to the application.
  2. Revalidate on Network Reconnect: If the application goes offline and then regains network connectivity, SWR automatically triggers a revalidation for all active data keys. This is a critical feature for mobile applications or environments with unstable network conditions, enhancing application resilience.
  3. Polling (Interval Revalidation): For data that changes frequently, SWR allows developers to configure a polling interval. This periodically re-fetches data in the background, ensuring near real-time updates for dashboards, stock tickers, or chat applications. This can be configured via the refreshInterval option in useSWR.

Manual Cache Invalidation and Mutation

While automatic revalidation is powerful, there are scenarios where explicit cache invalidation or mutation is necessary, especially after data modifications (POST, PUT, DELETE requests). The mutate function is SWR’s primary tool for this. It allows developers to programmatically update the cache for a specific key, either by providing new data directly or by triggering a revalidation:

  • Local Mutation (Optimistic UI): As discussed earlier, mutate(key, newData, false) updates the local cache immediately without triggering a revalidation. This is ideal for optimistic UI updates.
  • Revalidate: Calling mutate(key) without new data, or with true as the third argument, forces SWR to revalidate that specific key, fetching fresh data from the API.

For example, after a user successfully submits a form to create a new item, you would typically want to revalidate the list of items to reflect the change:

import useSWR, { mutate } from 'swr';
import { fetcher } from '../utils/fetcher';

interface Item {
  id: string;
  name: string;
}

export default function ItemList() {
  const { data: items } = useSWR<Item[]>('/api/items', fetcher);

  const handleAddItem = async (newItemName: string) => {
    // Optimistically update the UI
    mutate('/api/items', [...(items || []), { id: Date.now().toString(), name: newItemName }], false);

    try {
      await fetch('/api/items', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: newItemName }),
      });
      // Revalidate to fetch the actual new item with correct ID from server
      mutate('/api/items'); 
    } catch (error) {
      console.error("Failed to add item:", error);
      // Revert optimistic update or show error
      mutate('/api/items'); // Revert by re-fetching original data
    }
  };

  if (!items) return <div>Loading items...</div>;

  return (
    <div>
      <h3>Items</h3>
      <ul>
        {items.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      <button onClick={() => handleAddItem('New Item ' + Math.random().toFixed(2))}>Add Item</button>
    </div>
  );
}

From an infrastructure perspective, effective cache invalidation is paramount. Improperly managed caches can lead to stale data being served to users, causing inconsistencies and potential operational issues. SWR’s explicit mutate function provides the necessary control to ensure that when backend data changes, the client-side representation is updated promptly. This reduces the need for frequent full page refreshes, thereby lowering server load and improving API efficiency. It also allows for more nuanced caching strategies at the CDN and API Gateway layers, where long cache durations can be set for certain endpoints, relying on SWR to manage freshness on the client.

Performance Optimization and Scalability with SWR and Next.js

When architecting cloud-native applications, performance and scalability are non-negotiable. The combination of SWR and Next.js offers a powerful synergy that directly addresses these concerns, providing a foundation for highly performant and scalable web services.

Reduced Perceived Latency

SWR’s core ‘stale-while-revalidate’ mechanism is a direct attack on perceived latency. By instantly displaying cached data, users experience immediate UI feedback, even if the underlying data is momentarily stale. This dramatically improves the psychological aspect of performance, making applications feel faster and more responsive. For critical user journeys, such as checkout processes or search results, this can directly translate to higher conversion rates and lower bounce rates, which are key performance indicators for any business.

Efficient Network Utilization

SWR includes several features that optimize network usage:

  • Request Deduplication: As mentioned, SWR ensures that multiple concurrent requests for the same data key result in only one actual network call. This prevents redundant fetches, reducing unnecessary network traffic and alleviating load on backend APIs. In a microservices architecture, this can significantly reduce inter-service communication overhead and potential bottlenecks.
  • Conditional Fetching: By allowing fetches to be conditionally skipped (e.g., if a dependent ID is not yet available), SWR prevents unnecessary API calls, especially during complex component lifecycles or when data dependencies are not yet met.
  • Optimistic UI: By updating the UI immediately and sending the request in the background, SWR masks network latency. This isn’t just about user experience; it means the application can remain interactive while waiting for server responses, reducing the chances of users abandoning an action due to perceived slowness.

Complementing Next.js’s Rendering Strategies

Next.js offers various rendering strategies: Static Site Generation (SSG), Server-Side Rendering (SSR), and Client-Side Rendering (CSR). SWR integrates seamlessly with all of them:

  • SSG: For pages generated at build time, SWR can be used to fetch dynamic data that changes frequently. The initial page is blazing fast from the CDN, and SWR keeps the dynamic parts fresh on the client.
  • SSR: For pages requiring fresh data on each request, Next.js fetches data on the server. SWR can then hydrate its cache with this initial data using fallbackData, taking over subsequent client-side updates and revalidations. This avoids a second fetch on the client for the same data immediately after page load.
  • CSR: For client-rendered routes or components, SWR provides a robust and efficient way to manage all client-side data fetching, abstracting away loading states, error handling, and caching.

This flexibility allows architects to choose the optimal rendering strategy for each part of their application, with SWR providing a consistent and performant data fetching layer across the board. The ability to cache and revalidate data intelligently on the client reduces the load on Next.js server instances, allowing them to scale more efficiently. This is particularly important in serverless Next.js deployments, where reducing function execution time and invocations directly impacts operational costs.

Robust Error Handling and Retries

SWR includes built-in error handling and exponential backoff retry mechanisms. If an API request fails, SWR can automatically retry the request with increasing delays, preventing a flood of retries that could further strain an overloaded backend. This contributes significantly to the application’s resilience, making it more tolerant to transient network issues or temporary backend service disruptions. For cloud architects, this means fewer alerts for intermittent API failures and a more stable user experience even during minor service degradations.

Architectural Considerations for SWR in Distributed Systems

When deploying SWR-powered Next.js applications in distributed systems, particularly those hosted on cloud platforms like AWS or GCP, several architectural considerations become paramount. The goal is to maximize the benefits of SWR’s client-side caching while ensuring data consistency, scalability, and operational efficiency across the entire infrastructure.

API Design and Consistency

SWR relies heavily on consistent API endpoint keys for caching and revalidation. Therefore, a well-designed RESTful or GraphQL API is fundamental. API responses should be predictable, and endpoint URLs should accurately represent the data being fetched. Inconsistent API responses or variable endpoint structures can lead to cache misses or incorrect data being displayed. Implementing robust API versioning (e.g., /api/v1/users) is also crucial to prevent breaking changes from affecting cached data for older client versions. This directly impacts the reliability of data served to the client and the overall stability of the application.

For example, if a backend API is managed by a separate team, establishing a clear OpenAPI specification (Swagger) for all endpoints consumed by the Next.js frontend ensures that SWR keys and expected data structures remain consistent, reducing integration issues and unexpected cache behaviors.

Global SWR Configuration and Providers

For enterprise applications, it’s often necessary to have a global SWR configuration. This can include a default fetcher function, error handling strategies, revalidation options, and more. SWR provides the SWRConfig provider for this purpose. Wrapping your Next.js application with SWRConfig allows you to define these global defaults, ensuring consistent behavior across all useSWR hooks.

// pages/_app.tsx
import type { AppProps } from 'next/app';
import { SWRConfig } from 'swr';
import { fetcher } from '../utils/fetcher';

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <SWRConfig 
      value={{
        fetcher: fetcher, // Global fetcher function
        revalidateOnFocus: true, // Default to revalidate on window focus
        shouldRetryOnError: true, // Default to retry on error
        errorRetryInterval: 5000, // Retry every 5 seconds on error
        onError: (err, key) => {
          console.error(`SWR Error for key ${key}:`, err);
          // Log errors to a centralized logging service like Datadog or Sentry
        },
        // ... other global configurations
      }}
    >
      <Component {...pageProps} />
    </SWRConfig>
  );
}

export default MyApp;

This centralized configuration simplifies management and ensures that all components adhere to the desired data fetching policies, which is essential for maintaining consistency in large codebases and across multiple development teams. It also provides a single point for integrating with observability tools, allowing for centralized logging and monitoring of data fetching errors.

Server-Side Cache Hydration and State Management

When using Next.js’s SSR or SSG, it’s common to pre-fetch data on the server and then hydrate the SWR cache on the client. This can be done by passing fallback props to the SWRConfig provider or directly to individual useSWR hooks. This ensures a seamless transition from server-rendered content to client-side interactivity without a ‘flash of loading state’.

For instance, in a large application, you might have a global context or state management solution (like Redux, Zustand, or React Context) that holds some application-wide data. SWR can coexist with these solutions. SWR is excellent for remote data, while global state managers handle client-side-only state. Architecturally, it’s best to delineate responsibilities clearly: SWR for data fetched over the network that benefits from caching and revalidation, and other state managers for local UI state or complex client-side data flows that don’t fit SWR’s model.

The choice of state management should be pragmatic. For most remote data, SWR often suffices, reducing the need for more complex global state solutions. However, for deeply interconnected client-side state, a dedicated state manager might still be appropriate. The key is to avoid duplicating state management responsibilities and to ensure that data flows are clear and maintainable. This clear separation of concerns simplifies debugging, improves testability, and reduces the cognitive load for developers working on different parts of the application, ultimately contributing to a more stable and scalable system.

Error Handling, Retries, and Fallbacks for Robustness

In any production-grade application, robust error handling is not merely a feature, but a foundational requirement for reliability and a positive user experience. SWR provides comprehensive mechanisms to manage network errors, API failures, and other data fetching issues, ensuring that applications remain resilient even in the face of transient problems or unexpected server responses. From a cloud architect’s perspective, these capabilities are vital for maintaining system uptime and operational stability.

Built-in Error Handling

The useSWR hook returns an error object, which becomes populated if the fetcher function throws an exception or if the network request fails. This allows developers to display user-friendly error messages or fallback UIs. It’s crucial to implement a centralized error logging strategy, pushing these errors to services like Sentry, Datadog, or AWS CloudWatch Logs. This provides visibility into API health and allows for proactive monitoring and incident response.

import useSWR from 'swr';
import { fetcher } from '../utils/fetcher';

interface Product {
  id: string;
  name: string;
}

export default function ProductDetail({ productId }: { productId: string }) {
  const { data, error, isLoading } = useSWR<Product>(`/api/products/${productId}`, fetcher);

  if (error) {
    // Log error to a centralized service for monitoring
    console.error(`Failed to fetch product ${productId}:`, error);
    return (
      <div role="alert" className="text-red-600">
        <p>Error loading product details. Please try again later.</p>
        {/* Optionally, display a retry button */}
        <button onClick={() => window.location.reload()}>Refresh Page</button>
      </div>
    );
  }

  if (isLoading) return <div>Loading product...</div>;
  if (!data) return <div>Product not found.</div>;

  return (
    <div>
      <h2>{data.name}</h2>
      <p>Product ID: {data.id}</p>
    </div>
  );
}

Automatic Retries with Exponential Backoff

SWR intelligently handles transient errors by automatically retrying failed requests. By default, SWR implements an exponential backoff strategy, meaning it waits for progressively longer intervals between retries. This is a critical feature for distributed systems, as it prevents a cascade of retries from overwhelming a temporarily struggling backend service. The errorRetryInterval and errorRetryCount options allow fine-grained control over this behavior:

  • errorRetryInterval: The interval (in milliseconds) to wait before retrying a failed request.
  • errorRetryCount: The maximum number of times SWR will retry a failed request.
  • shouldRetryOnError: A boolean or function to control whether SWR should retry on error.

Configuring these parameters appropriately can significantly improve the fault tolerance of the client-side application, reducing the impact of brief API outages or network glitches. From an infrastructure perspective, this reduces the ‘thundering herd’ problem where many clients simultaneously retry, potentially exacerbating the issue on the server side.

Fallbacks and Stale Data Persistence

SWR’s ‘stale-while-revalidate’ model inherently provides a fallback mechanism by displaying cached data immediately. This means that even if a revalidation request fails, the user still sees the last known good data, preventing a blank or error-filled screen. This is a powerful form of graceful degradation. Furthermore, SWR allows for fallbackData to be provided explicitly, either via the SWRConfig or directly to useSWR. This is particularly useful for pre-populating the cache with server-rendered data (from getServerSideProps) or for providing sensible defaults when no network connection is available.

Another advanced pattern involves persisting SWR’s cache across page navigations or even browser sessions. While SWR’s default cache is in-memory, libraries like swr-indexeddb or custom cache providers can be used to store data in more persistent storage like IndexedDB or Local Storage. This allows for an even more robust offline experience and ensures that frequently accessed data is available instantly, even after a full page refresh or browser restart. For applications requiring high offline capabilities or extremely fast subsequent loads, this persistence strategy is invaluable. Implementing such persistence requires careful consideration of data sensitivity and cache expiration policies.

By leveraging these error handling, retry, and fallback mechanisms, developers can build SWR-powered Next.js applications that are not only fast but also exceptionally resilient, capable of handling real-world network and API instabilities without compromising the user experience or system reliability.

Monitoring and Observability for SWR-Powered Applications

For any cloud-architected application, effective monitoring and observability are critical for ensuring performance, reliability, and security. When working with SWR and Next.js, understanding how to monitor data fetching behavior, identify bottlenecks, and troubleshoot issues is paramount. The client-side nature of SWR’s operations requires a specific focus on integrating with frontend performance monitoring tools and backend API monitoring.

Client-Side Performance Monitoring (APM)

Tools like Google Lighthouse, Web Vitals, and Real User Monitoring (RUM) solutions (e.g., Datadog RUM, New Relic Browser, Sentry Performance) are essential for tracking the impact of SWR on user experience. Key metrics to monitor include:

  • First Contentful Paint (FCP) and Largest Contentful Paint (LCP): While Next.js SSR/SSG largely influences these, SWR’s fallbackData can prevent LCP regressions by ensuring immediate content display even during revalidation.
  • Interaction to Next Paint (INP) / First Input Delay (FID): SWR’s optimistic UI updates and background revalidation can improve perceived responsiveness, which positively impacts these metrics.
  • Cumulative Layout Shift (CLS): SWR’s ability to render stale data immediately can help prevent layout shifts that occur when new data suddenly arrives and causes UI elements to move.
  • Network Requests: Monitor the number and size of SWR-initiated network requests. Excessive requests or large payloads can indicate inefficient data fetching or API design issues. Ensure request deduplication is working as expected.

Integrating SWR’s onError and onSuccess callbacks with these APM tools allows for granular tracking of individual data fetches. For example, logging SWR errors to Sentry provides immediate visibility into client-side data fetching failures, including the SWR key and any associated error details. This is crucial for debugging and understanding the root cause of data-related issues in a production environment.

import { SWRConfig } from 'swr';
import { fetcher } from '../utils/fetcher';
// import * as Sentry from '@sentry/browser'; // Example Sentry integration

function MyApp({ Component, pageProps }) {
  return (
    <SWRConfig
      value={{
        fetcher: fetcher,
        onError: (err, key) => {
          console.error(`SWR Error for key ${key}:`, err);
          // Sentry.captureException(err, { extra: { swrKey: key } });
          // Push error to your centralized logging/monitoring system
        },
        onSuccess: (data, key, config) => {
          console.log(`SWR Success for key ${key}:`, data);
          // Log successful fetches for analytics or debugging purposes
        },
      }}
    >
      <Component {...pageProps} />
    </SWRConfig>
  );
}

Backend API Monitoring

While SWR optimizes client-side fetching, the health of your backend APIs remains paramount. Tools like Prometheus, Grafana, AWS CloudWatch, or GCP Operations Suite should monitor API endpoints for:

  • Latency: Track the response times of your APIs. High latency can negate SWR’s benefits, as fresh data will take longer to arrive.
  • Error Rates: Monitor 5xx and 4xx error rates. Spikes in these indicate issues with your backend services that SWR’s retries might mask but not solve.
  • Throughput: Track the number of requests per second to ensure your backend can handle the load generated by SWR revalidations and initial fetches.
  • Resource Utilization: Monitor CPU, memory, and network I/O of your backend servers or serverless functions. SWR’s request deduplication helps, but inefficient queries or database bottlenecks can still lead to resource exhaustion.

By correlating client-side SWR logs and metrics with backend API metrics, cloud architects can gain a holistic view of data flow and identify performance bottlenecks or reliability issues across the entire stack. For instance, if SWR is reporting frequent retries for a specific key, and backend monitoring shows increased latency for that corresponding API endpoint, it points directly to a backend performance issue requiring attention. This integrated approach to observability ensures that the benefits of SWR are fully realized and that any underlying infrastructure problems are quickly identified and addressed, maintaining the high availability and performance of the application.

Deployment Strategies and Infrastructure for SWR Next.js

Deploying SWR-powered Next.js applications requires a robust infrastructure strategy that capitalizes on Next.js’s rendering capabilities and SWR’s client-side optimizations. The goal is to maximize performance, scalability, and cost-efficiency in cloud environments. This often involves a combination of CDN, serverless functions, and optimized API services.

CDN Integration for Static Assets and SSG Pages

For Next.js applications, a Content Delivery Network (CDN) is indispensable. Static assets (images, CSS, JavaScript bundles) and pages generated via Static Site Generation (SSG) should be served directly from a CDN. This minimizes latency for initial page loads and reduces the load on your origin servers. Services like CloudFront (AWS), Cloudflare, or Vercel’s built-in CDN provide global distribution and caching, ensuring users receive content from the nearest edge location. SWR’s role here is to manage dynamic client-side data fetching after the initial static content is delivered, ensuring that only the most critical, frequently changing data bypasses the CDN’s static cache.

Serverless Functions for SSR and API Routes

Next.js’s Server-Side Rendering (SSR) and API Routes are perfectly suited for serverless environments. Deploying these as AWS Lambda functions, Google Cloud Functions, or Vercel functions allows for automatic scaling based on demand, pay-per-execution billing, and reduced operational overhead. When a user requests an SSR page, a serverless function executes, fetches necessary data (potentially from a database or another microservice), renders the HTML, and sends it to the client. SWR then takes over for subsequent client-side data fetching and revalidation.

For API Routes, SWR interacts directly with these serverless functions. This architecture is highly scalable: as client demand for data increases, the number of serverless function invocations scales automatically. This eliminates the need for manual server provisioning and management, crucial for agile development and rapid scaling. Consider using an API Gateway (like AWS API Gateway) in front of your serverless functions to handle request routing, authentication, throttling, and caching, further enhancing the resilience and security of your data fetching layer.

Database and Backend Service Optimization

While SWR optimizes the frontend, the performance of your backend databases and services remains critical. Slow database queries or inefficient microservices will bottleneck even the most optimized SWR setup. Implement:

  • Database Indexing: Proper indexing on frequently queried columns is fundamental.
  • Caching Layers: Introduce caching at the database level (e.g., Redis, Memcached) for frequently accessed, slowly changing data.
  • Connection Pooling: Manage database connections efficiently to prevent exhaustion under high load.
  • Query Optimization: Regularly review and optimize SQL queries or ORM operations.
  • Asynchronous Processing: For long-running tasks, use message queues (e.g., SQS, Kafka) to offload work, ensuring API responses are fast.

For example, if your Next.js application frequently fetches a list of popular products, and this data changes only every few minutes, you could cache the API response at the API Gateway level or within your backend service using Redis. SWR on the client would still revalidate, but the backend would serve the cached data, significantly reducing database load. This layered caching strategy is key to achieving high scalability and reliability.

Infrastructure as Code (IaC)

Managing the deployment of Next.js applications and their associated backend services in a cloud environment is best done with Infrastructure as Code (IaC) tools like Terraform or AWS CloudFormation. IaC ensures consistent, repeatable deployments, reduces human error, and facilitates version control of your infrastructure. This is especially important for managing environments (development, staging, production) and for implementing disaster recovery strategies. Using IaC to define your serverless functions, API Gateways, databases, and CDN configurations ensures that your entire SWR-powered Next.js ecosystem is robust, auditable, and easily scalable.

By thoughtfully combining these deployment strategies, cloud architects can build highly performant, resilient, and cost-effective SWR Next.js applications that can handle demanding workloads and deliver exceptional user experiences.

Security Best Practices for SWR and Next.js Data Fetching

Securing data fetching in Next.js applications utilizing SWR is paramount. While SWR itself is a client-side library and doesn’t inherently introduce server-side vulnerabilities, its interaction with backend APIs and client-side data exposure necessitates careful adherence to security best practices. From a cloud architect’s perspective, securing the entire data path, from client request to server response, is critical for protecting sensitive information and maintaining application integrity.

Authentication and Authorization

All API endpoints exposed to the Next.js frontend, especially those fetched by SWR, must be protected by robust authentication and authorization mechanisms. This typically involves:

  • Token-Based Authentication: Using JSON Web Tokens (JWTs) or OAuth 2.0 tokens passed in the Authorization header of SWR’s fetcher function. These tokens should be securely stored (e.g., in HTTP-only cookies to mitigate XSS risks) and refreshed regularly.
  • Role-Based Access Control (RBAC) / Attribute-Based Access Control (ABAC): Ensure that the backend API strictly enforces authorization rules, returning only the data that the authenticated user is permitted to access. Never rely solely on client-side checks for authorization.
// utils/authFetcher.ts
export const authFetcher = async (url: string) => {
  const token = localStorage.getItem('authToken'); // Or get from secure HTTP-only cookie
  const res = await fetch(url, {
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
  });

  if (res.status === 401 || res.status === 403) {
    // Handle unauthorized/forbidden access, e.g., redirect to login
    window.location.href = '/login';
    throw new Error('Unauthorized access');
  }
  if (!res.ok) {
    throw new Error('An error occurred.');
  }
  return res.json();
};

// components/SecureData.tsx
import useSWR from 'swr';
import { authFetcher } from '../utils/authFetcher';

export default function SecureData() {
  const { data, error } = useSWR('/api/secure-info', authFetcher);
  // ... rendering logic
}

Data Validation and Sanitization

Any data received from the client (e.g., form submissions, query parameters that influence SWR keys) must be thoroughly validated and sanitized on the server-side. This prevents common vulnerabilities such as SQL injection, Cross-Site Scripting (XSS), and command injection. Even if SWR is only fetching data, insecure API endpoints can be exploited to retrieve sensitive information or manipulate data on the backend. This is where backend frameworks like Laravel provide powerful validation tools. For example, ensuring that a user ID passed in a URL is indeed a valid UUID or integer before querying a database.

CORS Configuration

Carefully configure Cross-Origin Resource Sharing (CORS) on your backend APIs. Restrict allowed origins to only your Next.js application’s domain(s) to prevent unauthorized domains from making requests to your API. Misconfigured CORS can expose your API to malicious requests from arbitrary origins.

Protection Against Data Leakage

Ensure that your backend APIs do not expose sensitive data that is not intended for the client. Even if SWR doesn’t display it, an attacker inspecting network requests could potentially find sensitive information. This means implementing strict data filtering at the API layer. For instance, when fetching user profiles, only return public information unless specific authorization grants access to private details. This also applies to error messages: avoid leaking internal server details or stack traces in API error responses.

HTTPS Enforcement

Always enforce HTTPS for all communications between the Next.js client (and SWR) and your backend APIs. This encrypts data in transit, protecting against eavesdropping and man-in-the-middle attacks. Cloud providers and CDNs offer easy ways to enable and enforce HTTPS for both frontend applications and backend API endpoints.

Dependency Security

Regularly audit and update your project’s dependencies (including SWR, Next.js, and any fetching libraries) to patch known vulnerabilities. Tools like Dependabot, Snyk, or OWASP Dependency-Check can automate this process. Using secure package registries and verifying package integrity are also good practices.

By integrating these security best practices into the architecture and development lifecycle, cloud architects can ensure that SWR-powered Next.js applications remain secure and resilient against common web vulnerabilities, protecting both user data and system integrity.

SWR vs. React Query: Choosing the Right Data Fetching Library

While SWR is an excellent choice for data fetching in Next.js applications, it’s not the only option. React Query (now TanStack Query) is another prominent library that addresses similar problems, offering a different set of features and trade-offs. As a cloud architect, understanding these differences is crucial for making informed decisions that align with project requirements, team expertise, and long-term maintainability and scalability goals.

Core Philosophies and Features

Both SWR and React Query aim to simplify data fetching, caching, and synchronization in React applications. They both provide hooks that abstract away loading states, error handling, and revalidation logic. However, their core philosophies and feature sets diverge:

  • SWR: Embraces the ‘Stale-While-Revalidate’ strategy directly in its name. It prioritizes simplicity and a lean API. It’s often favored for its minimal configuration and straightforward approach to caching and revalidation. SWR’s cache is global and in-memory by default, making it easy to share data across components.
  • React Query: Offers a more comprehensive and opinionated approach to server-state management. It provides more advanced features out-of-the-box, such as automatic garbage collection of inactive queries, persistent caching, query invalidation based on complex dependencies, and a dedicated DevTools interface. React Query’s cache is also global but offers more granular control over individual queries.

Feature Comparison Table

Feature SWR React Query (TanStack Query)
Core Strategy Stale-While-Revalidate Stale-While-Revalidate (with more control)
API Simplicity High (minimal options) Moderate (more options, more powerful)
Cache Management Global, in-memory by default. Custom providers for persistence. Global, in-memory by default. Automatic garbage collection, persistent query client.
Query Invalidations mutate(key) for explicit revalidation. queryClient.invalidateQueries(key), more powerful patterns (e.g., query key prefixes).
Optimistic Updates Supported via mutate(key, newData, false). Supported via queryClient.setQueryData and queryClient.cancelQueries.
DevTools Limited built-in. Community extensions available. Dedicated, highly featured DevTools.
Bundle Size Smaller Larger (due to more features)
Server-Side Rendering (Next.js) Excellent, with fallbackData prop. Excellent, with dehydrate/hydrate functions.
Dependent Queries Conditional fetching (falsy key). enabled option, or composing hooks.
Mutation Handling mutate for revalidation after POST/PUT/DELETE. useMutation hook with lifecycle callbacks.

Architectural Implications and Choice Factors

  • Project Size and Complexity: For smaller projects or those with less complex data interactions, SWR’s simplicity often makes it the faster choice for implementation. For larger, data-intensive enterprise applications with intricate caching requirements, React Query’s extensive feature set and DevTools might provide more long-term benefits and better developer experience.
  • Team Familiarity: The expertise of your development team plays a significant role. If the team is already familiar with one library, the overhead of adopting another should be weighed against its perceived benefits.
  • Debugging and Observability: React Query’s dedicated DevTools offer a superior debugging experience for complex caching scenarios, which can be invaluable in production environments. While SWR has community tools, they are not as integrated. This can impact the time to diagnose and resolve data-related issues.
  • Bundle Size: For performance-critical applications where every kilobyte counts, SWR’s smaller bundle size might be a deciding factor, especially for mobile-first experiences.
  • Specific Features: If features like automatic garbage collection, robust query invalidation patterns (e.g., invalidating all queries starting with a prefix), or persistent caching across sessions are critical, React Query might be the more direct path. SWR can achieve some of these with custom cache providers or more manual logic, but React Query provides them out-of-the-box.

Ultimately, both SWR and React Query are excellent libraries. The choice should be driven by a careful assessment of the application’s specific needs, the complexity of its data requirements, and the operational preferences of the development and infrastructure teams. For many Next.js applications, SWR provides a highly effective and performant solution with minimal overhead, making it a strong contender for building scalable web services.

Migrating from Traditional Fetching to SWR in Next.js

Migrating an existing Next.js application from traditional data fetching methods (e.g., useEffect with `useState` or custom `fetch` wrappers) to SWR can significantly improve performance, simplify code, and enhance maintainability. This migration is an architectural improvement that consolidates data fetching logic, introduces intelligent caching, and streamlines error handling and loading states. As a cloud architect, understanding this migration path is key to modernizing applications and leveraging the full potential of Next.js.

Step 1: Identify Data Fetching Hotspots

Begin by identifying components that frequently fetch data, manage complex loading/error states, or suffer from perceived latency. These are prime candidates for SWR integration. Look for patterns like:

  • useEffect(() => { fetch(...) }, []): Manual fetching on component mount.
  • Custom hooks that replicate caching or revalidation logic.
  • Components with multiple `useState` calls for `data`, `loading`, `error`.

Prioritize critical user-facing components, such as dashboards, product listings, or user profiles, where an improved user experience will have the most impact.

Step 2: Define a Global Fetcher Function

Create a centralized fetcher function that all SWR hooks will use. This function should encapsulate your application’s standard data fetching logic, including:

  • HTTP client (e.g., `fetch` or `axios`).
  • Default headers (e.g., `Content-Type`, `Authorization`).
  • Common error handling (e.g., checking `res.ok`, parsing error bodies).
  • Token refresh logic if using JWTs.

Place this fetcher in a utility file (e.g., `utils/fetcher.ts`) and make it available application-wide, often by wrapping your `_app.tsx` with `SWRConfig`.

// utils/fetcher.ts
export const fetcher = async (url: string) => {
  const token = localStorage.getItem('jwt'); // Or retrieve from secure cookie
  const headers: HeadersInit = {
    'Content-Type': 'application/json',
  };
  if (token) {
    headers['Authorization'] = `Bearer ${token}`;
  }

  const res = await fetch(url, { headers });

  if (!res.ok) {
    const error = new Error('An API error occurred.');
    (error as any).info = await res.json();
    (error as any).status = res.status;
    throw error;
  }
  return res.json();
};

// pages/_app.tsx
import { SWRConfig } from 'swr';
import { fetcher } from '../utils/fetcher';

function MyApp({ Component, pageProps }) {
  return (
    <SWRConfig value={{ fetcher }}>
      <Component {...pageProps} />
    </SWRConfig>
  );
}

Step 3: Replace `useEffect` with `useSWR`

For each identified component, replace manual data fetching logic with the `useSWR` hook. This significantly reduces boilerplate code. The `data`, `error`, and `isLoading` properties returned by `useSWR` replace your custom state variables.

// Before (simplified)
import React, { useState, useEffect } from 'react';

function OldComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const res = await fetch('/api/items');
        const json = await res.json();
        setData(json);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };
    fetchData();
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  return <div>{JSON.stringify(data)}</div>;
}

// After migration
import useSWR from 'swr';
// fetcher is globally configured or imported

function NewComponent() {
  const { data, error, isLoading } = useSWR('/api/items');

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  return <div>{JSON.stringify(data)}</div>;
}

This refactoring not only cleans up the component code but also instantly benefits from SWR’s caching, revalidation, and deduplication features without additional effort.

Step 4: Hydrate SWR Cache from Server-Side Props

For Next.js pages that use `getServerSideProps` or `getStaticProps`, ensure that the data fetched on the server is used to hydrate SWR’s client-side cache. This is achieved by passing the data as `fallbackData` to `useSWR` or as `fallback` to `SWRConfig`.

// pages/posts/[id].tsx
import useSWR from 'swr';
import { GetServerSideProps } from 'next';

interface Post {
  id: string;
  title: string;
}

interface PostPageProps {
  post: Post;
}

export default function PostPage({ post: initialPost }: PostPageProps) {
  const { data: post, error, isLoading } = useSWR<Post>(`/api/posts/${initialPost.id}`, { 
    fallbackData: initialPost,
    revalidateOnMount: true // Optional: revalidate on mount for freshest data
  });

  if (error) return <div>Failed to load post.</div>;
  if (isLoading) return <div>Loading post...</div>;

  return (
    <div>
      <h1>{post?.title}</h1>
      <p>Post ID: {post?.id}</p>
    </div>
  );
}

export const getServerSideProps: GetServerSideProps = async (context) => {
  const { id } = context.params as { id: string };
  const initialPost = await fetcher(`http://localhost:3000/api/posts/${id}`); // Use your global fetcher
  return {
    props: {
      post: initialPost,
    },
  };
};

This step is crucial for maintaining excellent initial page load performance while delegating subsequent data freshness to SWR. The migration process should be iterative, focusing on one component or data fetching pattern at a time, allowing for thorough testing and validation of the improved data flow. By following these steps, organizations can systematically enhance the performance, reliability, and developer experience of their Next.js applications.

Case Study: SWR in a High-Traffic Logistics Dashboard

Consider a real-world scenario: a high-traffic logistics dashboard built with Next.js, used by hundreds of dispatchers simultaneously to monitor thousands of active shipments. The dashboard displays real-time vehicle locations, delivery statuses, driver availability, and alerts. Before SWR, this application struggled with several critical issues: slow data updates, high server load, and inconsistent UI states. The original implementation relied on polling every 10 seconds for all widgets using individual `useEffect` hooks and manual state management.

Initial Challenges and Bottlenecks

  1. Stale Data: Despite aggressive polling, data across different widgets would occasionally be out of sync due to independent fetch cycles and race conditions.
  2. High Server Load: Each client initiated numerous API calls every 10 seconds, leading to significant load spikes on the backend microservices, particularly the ‘Shipment Tracking’ and ‘Driver Status’ APIs. This resulted in increased infrastructure costs and occasional API timeouts during peak hours.
  3. Perceived Latency: Users experienced intermittent loading spinners when new data arrived, especially for complex widgets, leading to frustration and reduced productivity for dispatchers.
  4. Complex Client-Side Logic: Manual caching, deduplication, and error handling for each data source resulted in verbose, error-prone code that was difficult to maintain and extend.

SWR Implementation and Architectural Changes

The engineering team decided to refactor the data fetching layer using SWR. Key architectural changes included:

  • Centralized Fetcher: A global `fetcher` function was implemented within `_app.tsx` using `SWRConfig`, ensuring consistent authentication and error handling across all API calls. This also integrated with their existing `Laravel JSON Resource` structured API responses, simplifying data parsing.
  • `useSWR` for Core Data: All primary data fetching (e.g., `/api/shipments`, `/api/drivers`, `/api/alerts`) was migrated to `useSWR`. Each `useSWR` hook was configured with a `refreshInterval` of 5 seconds for critical real-time data, and 30 seconds for less volatile data, replacing the previous manual polling.
  • Dependent Fetching: For widgets displaying shipment details based on a selected driver, dependent SWR queries were used (`useSWR(driverId ? `/api/driver/${driverId}/shipments` : null, fetcher)`), preventing unnecessary fetches.
  • Optimistic UI for Actions: When a dispatcher updated a shipment status (e.g., ‘mark as delivered’), an optimistic UI update was implemented using `mutate`. The UI would instantly reflect the ‘delivered’ status, and then `mutate(‘/api/shipments’)` would be called to revalidate the main shipments list after the server response. This significantly improved the responsiveness of dispatcher actions.
  • Error Handling and Retries: SWR’s built-in error handling and exponential backoff retries were configured globally. This meant that transient network issues or temporary API degradations were gracefully handled on the client, reducing visible errors for dispatchers. Centralized SWR errors were also pushed to their monitoring system, alongside backend logs from `Laravel Pail`, for comprehensive observability.

Results and Impact

The migration to SWR yielded significant improvements:

  • Improved Data Freshness and Consistency: SWR’s revalidation-on-focus and intelligent polling ensured that data across all widgets was consistently fresh, with minimal perceived latency.
  • Reduced Server Load: Request deduplication and optimized polling intervals drastically reduced the number of redundant API calls. The peak load on backend services decreased by approximately 30%, leading to a noticeable reduction in cloud infrastructure costs for their AWS Lambda functions and RDS instances.
  • Enhanced User Experience: The ‘instant-on’ nature of SWR, combined with optimistic UI updates, made the dashboard feel significantly faster and more responsive. Dispatchers reported higher satisfaction and improved efficiency.
  • Simplified Codebase: The data fetching logic became much cleaner and more declarative, reducing the complexity of components and making the application easier to maintain and onboard new developers. The team could now focus more on business logic rather than boilerplate data management.
  • Increased Resilience: SWR’s retry mechanisms and stale data display ensured that the dashboard remained functional and informative even during brief backend service interruptions, improving the overall reliability of the system. This was crucial for an always-on operational tool.

This case study demonstrates how leveraging SWR in a Next.js application, especially in a high-traffic, data-intensive environment, can translate directly into tangible benefits for user experience, operational efficiency, and infrastructure cost management.

The integration of SWR with Next.js offers a powerful, architecturally sound approach to modern web development, addressing critical concerns around performance, scalability, and developer experience. By embracing the ‘Stale-While-Revalidate’ strategy, applications can deliver an ‘instant-on’ user experience, intelligently manage client-side caching, and significantly reduce the load on backend infrastructure. From optimizing initial page loads with Next.js’s SSR/SSG to enabling real-time updates and robust error handling with SWR, this combination provides a comprehensive solution for data fetching in complex, high-traffic systems.

As cloud architects, our focus is on building resilient, efficient, and maintainable systems. SWR contributes directly to these goals by abstracting away much of the boilerplate associated with data fetching, allowing development teams to concentrate on business logic. The benefits extend beyond code simplicity to tangible improvements in network utilization, server cost reduction, and enhanced user satisfaction. Adopting SWR is not just a technical choice; it’s a strategic decision to build web applications that are future-proof, performant, and capable of scaling with evolving demands.

Explore our complete Laravel, Basics directory for more guides.

NR Studio builds custom web apps, mobile apps, SaaS platforms, and internal tools for growing businesses. If you’re working through a technical decision, feel free to reach out — no commitment required.

References & Further Reading

Leave a Comment

Your email address will not be published. Required fields are marked *