### Basic FlyOut Component Usage
Source: https://www.patterns.dev/react/compound-pattern
Demonstrates how to use the FlyOut component with its Toggle sub-component. This example shows the minimal setup required to render the toggle button.
```javascript
import React from "react";
import { FlyOut } from "./FlyOut";
export default function FlyoutMenu() {
return (
);
}
```
--------------------------------
### Install Vercel AI SDK
Source: https://www.patterns.dev/react/ai-ui-patterns
Install the Vercel AI SDK to simplify integration with AI models. This package provides React hooks and server utilities for streaming and state management.
```bash
npm i ai
```
--------------------------------
### Minimal CSR Setup with React 18+
Source: https://www.patterns.dev/react/client-side-rendering
This snippet demonstrates the basic setup for a Client-Side Rendering application using React 18's `createRoot` API. It replaces the deprecated `ReactDOM.render` and enables Concurrent Rendering.
```javascript
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const container = document.getElementById("root");
const root = createRoot(container);
root.render(
);
```
--------------------------------
### Initialize Vite + React Project
Source: https://www.patterns.dev/react/ai-ui-patterns
Use this command to create a new Vite project with React template. This is a common starting point for many web applications.
```bash
npm create vite@latest my-ai-app -- --template react
```
--------------------------------
### React Server Component Example
Source: https://www.patterns.dev/react/progressive-hydration
This example demonstrates a React Server Component that renders static content on the server without shipping client-side JavaScript for those parts. Only the `ClientCounter` component requires hydration.
```javascript
// app/page.tsx -- Server Component by default
import ClientCounter from "./ClientCounter";
export default async function Home() {
const posts = await db.posts.findMany();
return (
Latest posts
{posts.map((p) =>
{p.title}
)} {/* no client JS */}
{/* the only thing that hydrates */}
);
}
```
--------------------------------
### Server Component Example
Source: https://www.patterns.dev/react/react-server-components
A default Server Component that fetches data and renders a list. It does not ship its rendering logic to the client.
```tsx
// app/posts/page.tsx -- Server Component (no directive)
import LikeButton from "./LikeButton";
export default async function Posts() {
const posts = await db.posts.findMany({ orderBy: { createdAt: "desc" } });
return (
{posts.map((post) => (
{post.title}
{post.excerpt}
))}
);
}
```
--------------------------------
### Client Component Example
Source: https://www.patterns.dev/react/react-server-components
A client component marked with 'use client' that uses React hooks and event handlers. Only this component's code ships to the client.
```tsx
// app/posts/LikeButton.tsx
"use client";
import { useState } from "react";
export default function LikeButton({ postId, initialCount }) {
const [count, setCount] = useState(initialCount);
return (
);
}
```
--------------------------------
### Streaming SSR Server Setup
Source: https://www.patterns.dev/react/streaming-ssr
Sets up an Express server to handle streaming SSR for a React application. It uses `renderToPipeableStream` to stream the HTML shell and then the content as it becomes available. Includes error handling and connection timeouts.
```jsx
import express from "express";
import { renderToPipeableStream } from "react-dom/server";
import Dashboard from "./Dashboard";
const app = express();
app.get("/", (req, res) => {
let didError = false;
const { pipe, abort } = renderToPipeableStream(, {
bootstrapModules: ["/static/client.js"],
onShellReady() {
res.statusCode = didError ? 500 : 200;
res.setHeader("Content-Type", "text/html");
pipe(res);
},
onShellError(error) {
res.statusCode = 500;
res.setHeader("Content-Type", "text/html");
res.send("
Dashboard unavailable
");
},
onError(error) {
didError = true;
console.error(error);
},
});
// Drop the connection if a client hangs for too long.
setTimeout(abort, 10_000);
});
app.listen(3000);
```
--------------------------------
### Server-Side Rendering with Streaming and Selective Hydration
Source: https://www.patterns.dev/react/react-selective-hydration
Use `pipeToNodeWritable` for streaming SSR. This example demonstrates how to integrate it with `Suspense` and a data provider to enable selective hydration and lazy loading of components.
```javascript
import { pipeToNodeStream} from "react-dom/server";
export function render(res) {
const data = createServerData();
const { startWriting, abort } = pipeToNodeWritable(
,
res,
{
onReadyToStream() {
res.setHeader('Content-type', 'text/html');
res.write('');
startWriting();
}
}
);
};
```
--------------------------------
### Express SSR with renderToPipeableStream
Source: https://www.patterns.dev/react/server-side-rendering
A minimal Express server for rendering a product detail page using React 18's `renderToPipeableStream`. This example demonstrates handling shell readiness, errors, and bootstrapping client-side modules for hydration.
```javascript
import express from "express";
import { renderToPipeableStream } from "react-dom/server";
import ProductPage from "./ProductPage";
const app = express();
app.use("/static", express.static("dist"));
app.get("/products/:id", async (req, res) => {
const product = await loadProduct(req.params.id);
let didError = false;
const { pipe } = renderToPipeableStream(
,
{
bootstrapModules: ["/static/client.js"],
onShellReady() {
res.statusCode = didError ? 500 : 200;
res.setHeader("Content-Type", "text/html");
pipe(res);
},
onShellError(err) {
res.statusCode = 500;
res.setHeader("Content-Type", "text/html");
res.send("
Something went wrong
");
},
onError(err) {
didError = true;
console.error(err);
},
}
);
});
app.listen(3000);
```
--------------------------------
### Interaction-based Hydration with Lazy Loading
Source: https://www.patterns.dev/react/progressive-hydration
Defer loading heavy components until user interaction, like a click, to improve initial load performance. This example uses `lazy` and `Suspense` for a search modal trigger.
```javascript
"use client";
import { useState, lazy, Suspense } from "react";
const SearchModal = lazy(() => import("./SearchModal"));
export function SearchTrigger() {
const [open, setOpen] = useState(false);
return (
<>
{open && (
Loading search...}>
setOpen(false)} />
)}
>
);
}
```
--------------------------------
### Implementing Optimistic UI Updates with `useOptimistic`
Source: https://www.patterns.dev/react/hooks-pattern
Use `useOptimistic` to provide instant UI feedback for user actions before the server confirms. It allows for immediate visual updates that are later reconciled with the actual server response. This example shows a like button.
```javascript
import { useOptimistic, useTransition } from "react";
function LikeButton({ post }: { post: Post }) {
const [optimisticLikes, addOptimistic] = useOptimistic(
post.likes,
(current, delta: number) => current + delta,
);
const [, startTransition] = useTransition();
return (
);
}
```
--------------------------------
### App Router Page with Suspense for Dynamic Content
Source: https://www.patterns.dev/react/static-rendering
This example demonstrates a Next.js App Router page using `Suspense` to stream dynamic content. The static parts are rendered immediately, while the dynamic `GreetingForUser` component streams in later.
```typescript
// app/page.tsx
import { Suspense } from "react";
import { cookies } from "next/headers";
export const experimental_ppr = true;
async function GreetingForUser() {
const cookieStore = await cookies(); // dynamic
const session = cookieStore.get("session");
const name = session ? await lookupName(session.value) : "there";
return
Hello, {name}
;
}
export default function Home() {
return (
Welcome to the store
{/* static */}
{/* static */}
Hello...}>
{/* dynamic, streamed */}
{/* static, data fetched at build */}
);
}
```
--------------------------------
### Dynamic Rendering with Request-Time Data in Next.js
Source: https://www.patterns.dev/react/server-side-rendering
This example demonstrates how reading request-time data like cookies automatically opts a route into dynamic rendering in Next.js App Router. The page fetches user session and widgets based on cookies.
```typescript
import { cookies } from "next/headers";
export default async function Dashboard() {
const cookieStore = await cookies();
const session = cookieStore.get("session");
const user = await fetchUser(session?.value);
const widgets = await fetchWidgets(user.id);
return (
Welcome back, {user.name}
);
}
```
--------------------------------
### Using a Custom Hook for Feature Flagging
Source: https://www.patterns.dev/react/hoc-pattern
This example demonstrates how to use a custom hook `useFlag` to conditionally render different components based on a feature flag. The logic is contained within the component, making it easy to read and debug.
```javascript
function PricingPage(props: PricingPageProps) {
const showRedesign = useFlag("pricing_redesign_2025");
return showRedesign ? : ;
}
```
--------------------------------
### Form Usage with Render Props
Source: https://www.patterns.dev/react/render-props-pattern
This example shows how to use the `FormValidator` component. The parent component provides initial values, validation logic, and an onSubmit handler, and uses the `children` prop to render the form elements based on the state and handlers provided by `FormValidator`.
```jsx
({
email: v.email.includes("@") ? undefined : "Not an email",
password: v.password.length >= 8 ? undefined : "Too short",
})}
onSubmit={(v) => signIn(v)}
>
{({ values, errors, isValid, setField, submit }) => (
)}
```
--------------------------------
### Initialize Next.js Project
Source: https://www.patterns.dev/react/ai-ui-patterns
Use this command to create a new Next.js project. This is an alternative to Vite for building React applications, especially those requiring server-side rendering or static site generation.
```bash
npx create-next-app
```
--------------------------------
### Get Root Element
Source: https://www.patterns.dev/react/compound-pattern
This snippet shows how to get the root DOM element for a React application.
```javascript
const rootElement = document.getElementById("root");
```
--------------------------------
### Client Component Receiving Server Component as Prop
Source: https://www.patterns.dev/react/react-server-components
Demonstrates the correct pattern where a Client Component accepts a Server Component via props (e.g., as children or a specific prop). This is the recommended approach for composition.
```javascript
"use client";
export default function ClientLayout({ sidebar, children }) {
return (
{sidebar}
{children}
);
}
```
--------------------------------
### Sample Tweet Data Structure
Source: https://www.patterns.dev/react
This is an example of the JSON data structure used for tweets, including category, retweets, local status, and text content.
```json
[
{
"category": "Entertainment",
"retweets": "54",
"isLocal": false,
"text": "Omg. A tweet."
},
{
"category": "Entertainment",
"retweets": "100",
"isLocal": false,
"text": "Omg. Another."
},
{
"category": "Technology",
"retweets": "32",
"isLocal": false,
"text": "New ECMAScript features!"
},
{
"category": "Technology",
"retweets": "88",
"isLocal": true,
"text": "Wow, learning React!"
}
]
```
--------------------------------
### HOC with Prop Name Collision Example
Source: https://www.patterns.dev/react/hoc-pattern
Demonstrates a HOC that injects a 'theme' prop. Parent-supplied props are spread last, meaning they will overwrite injected props of the same name.
```typescript
function withTheme
(Wrapped: React.ComponentType
) {
return (props: Omit
) => {
const theme = useTheme();
// Parent-supplied props win because they are spread last
return ;
};
}
```
--------------------------------
### Create FlyOut Context and Provider
Source: https://www.patterns.dev/react/compound-pattern
Initializes the context and provides the state (open, toggle) to its children via FlyOutContext.Provider. This is the core of the state management for the compound component.
```javascript
const FlyOutContext = createContext();
function FlyOut(props) {
const [open, toggle] = useState(false);
return (
{props.children}
);
}
```
--------------------------------
### Time-based Revalidation at Route Level
Source: https://www.patterns.dev/react/incremental-static-rendering
Set the revalidation interval for an entire route by exporting the `revalidate` constant. This example re-renders the page at most once every 5 minutes (300 seconds).
```typescript
// app/blog/page.tsx
// Re-render at most once every 5 minutes.
export const revalidate = 300;
export default async function BlogIndex() {
const posts = await getAllPosts();
return ;
}
```
--------------------------------
### Basic Static Page
Source: https://www.patterns.dev/react/static-rendering
A simple server component without dynamic data sources is automatically statically rendered. No special functions like getStaticProps are needed.
```typescript
// app/pricing/page.tsx
export default function Pricing() {
return (
Pricing
Three tiers, no surprises.
);
}
```
--------------------------------
### useReducer Hook for Complex State Logic
Source: https://www.patterns.dev/react/hooks-pattern
Prefer useReducer for state with multiple sub-values that update together. The reducer function centralizes transitions and makes them easier to unit-test. Initialize state with a starting value.
```typescript
type State = { status: "idle" | "loading" | "ok" | "error"; data?: Order[]; error?: Error };
type Action =
| { type: "fetch" }
| { type: "success"; data: Order[] }
| { type: "failure"; error: Error };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "fetch": return { status: "loading" };
case "success": return { status: "ok", data: action.data };
case "failure": return { status: "error", error: action.error };
}
}
function Orders() {
const [state, dispatch] = useReducer(reducer, { status: "idle" });
// ...
```
--------------------------------
### Create List and Item Components
Source: https://www.patterns.dev/react/compound-pattern
Defines the List and Item components that conditionally render based on the 'open' state from the context. The List component wraps its children in a
if open, and Item renders a
.
```javascript
function List({ children }) {
const { open } = React.useContext(FlyOutContext);
return open &&
{children}
;
}
function Item({ children }) {
return
{children}
;
}
```
--------------------------------
### Using `use` to Read Promises and Context in React
Source: https://www.patterns.dev/react/hooks-pattern
The `use` hook suspends rendering until a promise resolves or reads context. It can be used conditionally without violating hook rules. This example shows fetching and displaying comments.
```javascript
import { use, Suspense } from "react";
function Comments({ commentsPromise }: { commentsPromise: Promise }) {
// Suspends here until the promise settles
const comments = use(commentsPromise);
return
{comments.map((c) =>
{c.text}
)}
;
}
export default function Post({ id }: { id: string }) {
const commentsPromise = fetchComments(id); // started during render
return (
Loading…}>
);
}
```
--------------------------------
### Define a Simple Route with TanStack Router
Source: https://www.patterns.dev/react/react-2026
This snippet demonstrates how to programmatically create a router with a single index route using TanStack Router. It highlights the code-centric approach and type safety offered by the library.
```javascript
import { createRootRoute, createRoute, createRouter, RouterProvider } from '@tanstack/react-router';
// Define a root route and a child route
const rootRoute = createRootRoute();
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () =>
Hello, world!
, // component to render at this route
});
// Compose the route tree and create the router
const routeTree = rootRoute.addChildren([indexRoute]);
const router = createRouter({ routeTree });
export default function App() {
return ; // provide the router to the app
}
```
--------------------------------
### Client Component Importing Server Component (Error)
Source: https://www.patterns.dev/react/react-server-components
Illustrates the incorrect way a Client Component might try to import a Server Component directly, which leads to an error.
```javascript
"use client";
import ServerSidebar from "./ServerSidebar"; // ERROR
export default function ClientLayout({ children }) {
return (
{children}
);
}
```
--------------------------------
### Server Component Composing Client and Server Components
Source: https://www.patterns.dev/react/react-server-components
Shows how a parent Server Component correctly composes a Client Layout component, passing a Server Component as a prop to it.
```javascript
// app/layout.tsx
import ClientLayout from "./ClientLayout";
import ServerSidebar from "./ServerSidebar";
export default function RootLayout({ children }) {
return (
}>{children}
);
}
```
--------------------------------
### Managing Form Submission State with `useActionState`
Source: https://www.patterns.dev/react/hooks-pattern
Use `useActionState` to simplify form state management. It returns the latest result, a wrapped action, and a pending flag, reducing boilerplate `useState` calls. This example demonstrates a newsletter subscription form.
```javascript
import { useActionState } from "react";
async function subscribeAction(_prev: State, formData: FormData) {
const email = formData.get("email") as string;
try {
await subscribe(email);
return { ok: true } as const;
} catch (err) {
return { ok: false, error: (err as Error).message } as const;
}
}
function NewsletterForm() {
const [state, formAction, isPending] = useActionState(subscribeAction, { ok: false });
return (
);
}
```
--------------------------------
### Conditional Rendering with onShellReady and onAllReady
Source: https://www.patterns.dev/react/streaming-ssr
Detect user-agent to choose between `onShellReady` for immediate shell flushing or `onAllReady` for complete tree rendering. This pattern is useful for optimizing delivery to different clients like browsers and crawlers.
```javascript
const isCrawler = /bot|crawler|spider|crawling/i.test(req.headers["user-agent"] || "");
const { pipe } = renderToPipeableStream(, {
bootstrapModules: ["/static/client.js"],
[isCrawler ? "onAllReady" : "onShellReady"]() {
res.statusCode = didError ? 500 : 200;
res.setHeader("Content-Type", "text/html");
pipe(res);
},
onError(err) {
didError = true;
console.error(err);
},
});
```
--------------------------------
### Static Page with Client-Side Fetch for Dynamic Content
Source: https://www.patterns.dev/react/static-rendering
Render most of a page statically while fetching a dynamic slice (e.g., personalized recommendations) on the client after hydration. This pattern maintains static TTFB for the main content while providing fresh, per-user data where needed.
```tsx
// app/products/[id]/page.tsx
import RecommendationsClient from "./RecommendationsClient";
export async function generateStaticParams() {
const products = await getAllProducts();
return products.map((p) => ({ id: p.id }));
}
export default async function Product({ params }) {
const { id } = await params;
const product = await getProduct(id);
return (
<>
{/* hydrated separately, fetches at runtime */}
>
);
}
```
--------------------------------
### Using Vercel AI Elements for Chat UI
Source: https://www.patterns.dev/react/ai-ui-patterns
Demonstrates how to integrate Vercel AI Elements for a chat interface. It uses the `Conversation`, `Prompt`, and `TypingIndicator` components along with the `useChat` hook for managing chat state.
```javascript
import { Conversation, Prompt, TypingIndicator } from '@vercel/ai-elements';
function ChatApp() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
return (
{isLoading && }
);
}
```
--------------------------------
### Consumer Using Geolocation Component
Source: https://www.patterns.dev/react/render-props-pattern
Demonstrates how a consumer component uses the Geolocation component by providing a render prop to define the UI based on geolocation state.
```jsx
{
if (status === "pending") return ;
if (error) return ;
return ;
}}
/>
```
--------------------------------
### Enable Partial Prerendering
Source: https://www.patterns.dev/react/static-rendering
Enable Partial Prerendering by setting `experimental_ppr = true` in your `app/page.tsx` file. This enables route-by-route opt-in.
```typescript
export const experimental_ppr = true;
```
--------------------------------
### Importing Container Component
Source: https://www.patterns.dev/react/presentational-container-pattern
This code snippet shows how to import the main container component for the application. It's typically placed in the entry point of your React application.
```javascript
import DogImagesContainer from "./DogImagesContainer";
```
--------------------------------
### Use useMediaQuery Hook for Motion Awareness
Source: https://www.patterns.dev/react/hooks-pattern
Shows how to use the useMediaQuery hook to conditionally render different components based on the 'prefers-reduced-motion' media query.
```typescript
// Usage
function MotionAwareIntro() {
const reduceMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
return reduceMotion ? : ;
}
```
--------------------------------
### Render Root App Component
Source: https://www.patterns.dev/react/presentational-container-pattern
This snippet shows the standard way to render the root App component in a React application using ReactDOM.
```javascript
render(, document.getElementById("root"));
```
--------------------------------
### Basic Compound Component Usage
Source: https://www.patterns.dev/react/compound-pattern
Demonstrates the typical usage of a compound component in React. Ensure the FlyOut component and its children (Toggle, List, Item) are correctly defined and imported.
```javascript
import { FlyOut } from "./FlyOut";
export default function FlyoutMenu() {
return (
EditDelete
);
}
```
--------------------------------
### Streaming Server-Side Rendering with renderToReadableStream on Edge Runtimes
Source: https://www.patterns.dev/react/streaming-ssr
Use `renderToReadableStream` for server-side rendering in edge runtimes that support Web Streams. Await `stream.allReady` to ensure the entire tree is rendered, similar to `onAllReady`, or omit it to stream the shell as early as possible.
```javascript
// edge-handler.jsx
import { renderToReadableStream } from "react-dom/server";
import App from "./App";
export default {
async fetch(request) {
let didError = false;
const stream = await renderToReadableStream(, {
bootstrapModules: ["/static/client.js"],
onError(err) {
didError = true;
console.error(err);
},
});
// Wait for the shell before responding — analogous to onShellReady.
await stream.allReady; // omit this to flush as early as possible
// Or, for crawlers, wait for the whole tree:
// await stream.allReady;
return new Response(stream, {
status: didError ? 500 : 200,
headers: { "content-type": "text/html" },
});
},
};
```
--------------------------------
### Presentational Component for Dog Images
Source: https://www.patterns.dev/react/presentational-container-pattern
This component receives an array of dog image URLs via props and renders them as img tags. It focuses solely on the presentation of the data.
```javascript
import React from "react";
export default function DogImages({ dogs }) {
return dogs.map((dog, i) => );
}
```
--------------------------------
### Exporting a Component with HOC
Source: https://www.patterns.dev/react/hoc-pattern
Demonstrates how to apply the `withAnalytics` HOC to a `CheckoutPage` component. The HOC is applied at the export site, passing the component and an event name.
```typescript
function CheckoutPage(props: CheckoutPageProps) {
return {/* ...checkout UI... */};
}
export default withAnalytics(CheckoutPage, "checkout_viewed");
```
--------------------------------
### Manual Stream Reading (Client-side Pseudocode)
Source: https://www.patterns.dev/react/ai-ui-patterns
This pseudocode demonstrates how to manually read a stream of data from an API response on the client-side. It decodes chunks and updates the UI incrementally.
```javascript
// Pseudocode for manual stream reading (client-side)
const res = await fetch('/api/chat', { method: 'POST', body: JSON.stringify({ messages }) });
const reader = res.body.getReader();
const decoder = new TextDecoder();
let partial = "";
while(true) {
const { value, done } = await reader.read();
if (done) break;
partial += decoder.decode(value);
setAssistantMessage(partial); // update the latest assistant message with new text
}
```
--------------------------------
### Use useLocalStorage Hook in a Component
Source: https://www.patterns.dev/react/hooks-pattern
Demonstrates how to use the useLocalStorage hook, providing a familiar interface similar to useState for managing theme preferences.
```typescript
function ThemeToggle() {
const [theme, setTheme] = useLocalStorage<"light" | "dark">("theme", "light");
return (
);
}
```
--------------------------------
### Static Rendering with Data Fetching
Source: https://www.patterns.dev/react/static-rendering
Server components fetching data are also statically rendered at build time. Next.js caches the data fetch result, running it once on the build server.
```typescript
// app/blog/page.tsx
import Link from "next/link";
export default async function BlogIndex() {
const posts = await getAllPosts();
return (
{posts.map((post) => (
{post.title}
))}
);
}
```
--------------------------------
### Dynamic Routes with generateStaticParams
Source: https://www.patterns.dev/react/static-rendering
Use `generateStaticParams` to define dynamic routes for static site generation. Export this function from a dynamic-segment route file to generate one HTML file per returned parameter set. Set `dynamicParams = false` to reject unknown slugs.
```tsx
// app/blog/[slug]/page.tsx
import { notFound } from "next/navigation";
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
// Reject params not returned above. Default in Next 15 is true.
export const dynamicParams = false;
export default async function Post({ params }) {
const { slug } = await params; // params is async in Next 15
const post = await getPost(slug);
if (!post) notFound();
return (
{post.title}
);
}
```
--------------------------------
### Fetching and Caching Server Data with TanStack Query
Source: https://www.patterns.dev/react/react-2026
Use the `useQuery` hook from TanStack Query to declaratively fetch and cache data from an API. This hook handles loading and error states automatically.
```javascript
import { useQuery } from '@tanstack/react-query';
function TodoList() {
const { data: todos, error, isLoading } = useQuery(['todos'], fetchTodos);
if (isLoading) return
Loading...
;
if (error) return
Error: {error.message}
;
return
{todos.map(t =>
{t.title}
)}
;
}
```
--------------------------------
### Implement useMediaQuery Hook
Source: https://www.patterns.dev/react/hooks-pattern
This hook allows components to react to CSS media queries, enabling adaptive UI based on user preferences like reduced motion or color scheme. It uses useState and useEffect to manage the media query state.
```typescript
import { useEffect, useState } from "react";
export function useMediaQuery(query: string) {
const [matches, setMatches] = useState(() =>
typeof window === "undefined" ? false : window.matchMedia(query).matches,
);
useEffect(() => {
const mql = window.matchMedia(query);
const onChange = (e: MediaQueryListEvent) => setMatches(e.matches);
mql.addEventListener("change", onChange);
return () => mql.removeEventListener("change", onChange);
}, [query]);
return matches;
}
```
--------------------------------
### Configure OpenAI API Key in Next.js
Source: https://www.patterns.dev/react/ai-ui-patterns
Store your OpenAI API key in the .env.local file for Next.js applications. This ensures the key is not exposed in client-side code.
```bash
OPENAI_API_KEY=sk-...
```