Basic TypeScript

Purpose and Scope

The Basic TypeScript example teaches the same mental model as the basic SWR example, but with explicit data shapes and typed fetch helpers. SWR is introduced in the repository README as a React Hooks library for data fetching that returns cached data first, revalidates in the background, and then updates the component with fresh data. In TypeScript, the important addition is not a different runtime workflow; it is making the expected response type visible at the hook boundary so loading, rendering, and helper functions stay aligned with the API response.

Sources: README.md, e2e/site/app/basic-ssr/block.tsx

This page focuses on the smallest useful TypeScript path: import the default hook, provide a stable request key, write an asynchronous fetcher, and annotate the data returned by the hook. The official Basic TypeScript example describes its idea as showing how to type the data received from SWR. The E2E site gives a compact source-backed version of that pattern by calling the hook with a string generic, receiving data that may initially be absent, and rendering a fallback while the request is still unresolved.

Sources: e2e/site/app/basic-ssr/block.tsx, README.md

Relevant Source Files

  • README.md - Introduces SWR, the stale-while-revalidate lifecycle, the quick-start hook call, the meaning of the key and fetcher, and the standard loading and error rendering pattern.
  • e2e/site/app/basic-ssr/block.tsx - Shows a client component using TypeScript with useSWR, a typed fetcher parameter, a JSON response transform, and a render fallback for undefined data.
  • e2e/site/app/basic-ssr/page.tsx - Shows the page-level wrapper that renders the typed client block in the Next.js app route.
  • e2e/site/app/render-preload-basic/page.tsx - Shows a TypeScript client page combining useSWR, preload, Suspense, typed props, state, and effects around a simple string response.
  • e2e/site/README.md - Provides the development-server workflow for running the Next.js E2E site locally while inspecting these examples.

Core Primitives

The first primitive is the request key. In the README quick start, the key is described as a unique identifier for the request, normally the API URL. In a TypeScript component, that key still drives both fetching and cache lookup, so the type annotation belongs beside the hook rather than inside unrelated render code. The second primitive is the fetcher, an asynchronous function that receives the key and resolves to data. The third primitive is the hook result, whose data starts absent, then becomes populated after the fetcher completes.

Sources: README.md

A minimal TypeScript version can make the response contract explicit with a domain type or a primitive generic. The E2E block uses a primitive string result: the fetcher receives a string URL, performs fetch and JSON parsing, then returns a name field from the response. Because the hook is called with a string result type, the rendered data is treated as a string when present. The component still accounts for the initial unresolved state by displaying a fallback label before the value arrives.

Sources: e2e/site/app/basic-ssr/block.tsx

import useSWR from 'swr'
 
type User = {
  name: string
}
 
const fetcher = async (url: string): Promise<User> => {
  const response = await fetch(url)
  return response.json()
}
 
export function Profile() {
  const { data, error, isLoading } = useSWR<User>('/api/user', fetcher)
 
  if (error) return <div>failed to load</div>
  if (isLoading) return <div>loading...</div>
  return <div>hello {data?.name}!</div>
}

System-to-Code Mapping

The README quick start and the TypeScript E2E block map directly to the same three-step data flow. First, React renders a component and calls the hook with a key. Second, SWR invokes the fetcher asynchronously and keeps the component in a loading or undefined-data state while the request is pending. Third, when the fetcher resolves, SWR stores the result and rerenders the component. The TypeScript-specific choice is where to declare the returned data shape so that the render path and fetch helper share one contract.

Sources: README.md, e2e/site/app/basic-ssr/block.tsx

The page route in the E2E site is intentionally thin: it imports the client block and returns it as the page content. That separation is useful when adapting the basic example to a Next.js app structure, because the SWR hook runs inside a client component while the route file can stay focused on composition. For readers building their own TypeScript examples, this means the typed hook, browser fetch call, and loading UI should live in the client-facing component that owns the data dependency.

Sources: e2e/site/app/basic-ssr/page.tsx, e2e/site/app/basic-ssr/block.tsx

Execution Flow

Start by installing and running the example project using the documented example workflow, or by running the E2E site when working inside the repository. The official example instructions use either Yarn or npm, while the E2E site README describes starting the Next.js development server and opening the local app in a browser. Once the page is running, inspect the first render separately from the resolved render. The typed data is not available immediately, so the UI must be prepared for loading or undefined data.

Sources: e2e/site/README.md, README.md

npm run dev
# or
yarn dev
# or
pnpm dev

When implementing the component, write the fetcher as a narrow adapter from the key to the data shape your UI needs. The E2E block fetcher accepts the URL string, parses JSON, and returns the nested name value instead of exposing the entire response object to the component. That is a practical TypeScript pattern: keep response normalization near the network boundary, then let the component render a simpler type. If the API later changes, the mismatch appears in the helper rather than spreading through the JSX.

Sources: e2e/site/app/basic-ssr/block.tsx

Preload and Client Rendering Notes

The render-preload page shows another TypeScript-friendly pattern that remains close to the basic example: a shared key, a fetcher that resolves to a simple string, and a component that renders the hook result. It adds a preload step inside an effect before rendering the children, then reads the same key through the normal hook. This demonstrates that preloading does not replace the typed data flow. The key and fetcher still define the cache entry, and the component still consumes the resolved value through the hook.

Sources: e2e/site/app/render-preload-basic/page.tsx

That page also illustrates two common surrounding concerns in TypeScript React code. The preload wrapper types its children as React nodes, and local state tracks how many times the fetcher has run. Suspense is present with a fallback, but the core data contract remains simple: the fetcher returns a string, and the rendered output displays either the current value or an empty fallback. For a basic TypeScript example, treat preload, Suspense, and state as optional extensions around the same typed hook foundation.

Sources: e2e/site/app/render-preload-basic/page.tsx

Implementation Checklist

Before copying the pattern into an application, define the smallest response type the component actually needs. If the API returns a large object but the page only renders a name, either type the full response and select the field deliberately, or make the fetcher return a smaller view model. Then call the hook with the expected result type, handle errors, handle the loading state, and avoid assuming data exists during the first render. This matches the README description of data being undefined while the fetcher is not finished.

Sources: README.md, e2e/site/app/basic-ssr/block.tsx

A useful next step is to compare this page with the broader basic data-fetching recipe and the API reference for the primary hook. The basic recipe emphasizes fetching API data across pages, while this TypeScript-focused version emphasizes the contract between the fetcher and the data returned to JSX. If you later need shared fetch behavior, move the typed fetch helper into a module or wrap the hook in a custom domain hook so each component receives a well-named, well-typed result.

Sources: README.md