FeedFusionDocs
Get started free

Next.js Integration

Embed a FeedFusion Instagram feed in a Next.js App Router project. This guide covers the server component pattern with ISR caching — the right approach for feed content that updates periodically and doesn't require real-time rendering on every request.

Fetch the feed

Use a server component that fetches the feed with next: { revalidate } for ISR caching. The API key stays on the server and is never sent to the browser.

Set your API key as an environment variable:

.env.local
bash
# .env.local
FEEDFUSION_API_KEY=ff_live_abc123

Then fetch the feed in a server component:

app/instagram-feed/page.tsx
tsx
import Image from "next/image";

type Post = {
  id: string;
  mediaUrl: string;
  thumbnailUrl: string | null;
  permalink: string;
  caption: string;
  mediaType: "IMAGE" | "VIDEO" | "CAROUSEL_ALBUM";
};

async function getFeed() {
  const res = await fetch(
    "https://api.feedfusion.dev/v1/feed/feed_abc123",
    {
      headers: { Authorization: `Bearer ${process.env.FEEDFUSION_API_KEY}` },
      next: { revalidate: 3600 },
    }
  );
  if (!res.ok) throw new Error("Failed to fetch feed");
  return res.json() as Promise<{ data: Post[] }>;
}

export default async function InstagramFeed() {
  const { data: posts } = await getFeed();

  return (
    <section className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4">
      {posts.map((post) => (
        <a
          key={post.id}
          href={post.permalink}
          target="_blank"
          rel="noopener noreferrer"
          className="group relative aspect-square overflow-hidden rounded-lg"
        >
          <Image
            src={post.thumbnailUrl ?? post.mediaUrl}
            alt={post.caption?.slice(0, 120) ?? "Instagram post"}
            fill
            className="object-cover transition-transform group-hover:scale-105"
            sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw"
          />
        </a>
      ))}
    </section>
  );
}

Client component pattern

If your feed component needs client-side interactivity — hover effects, a lightbox, click handlers — split the work: fetch in a server component and pass the result as props to your client component. The API call stays on the server; the UI layer gets full React interactivity.

app/instagram-section/page.tsx
tsx
// Server component — fetches the data
import { InstagramGallery } from "./instagram-gallery";

export default async function InstagramSection() {
  const res = await fetch(
    "https://api.feedfusion.dev/v1/feed/feed_abc123",
    {
      headers: { Authorization: `Bearer ${process.env.FEEDFUSION_API_KEY}` },
      next: { revalidate: 3600 },
    }
  );
  const { data: posts } = await res.json();
  return <InstagramGallery posts={posts} />;
}

Caching with ISR

Instagram posts don't need real-time rendering. Setting revalidate: 3600 means Next.js re-fetches the feed at most once per hour, serving cached HTML on every request in between. This reduces your API call volume and makes every page load fast regardless of Instagram's response time.

Adjust the revalidate value based on how frequently your client posts:

  • Active accounts (daily posts): revalidate: 3600 (1 hour) is appropriate.
  • Less active accounts (weekly posts): revalidate: 86400 (24 hours) keeps API calls minimal.
  • Real-time requirement: Set revalidate: 0 for dynamic server render. Useful for accounts that post multiple times per day where freshness matters.

TypeScript types

The Post type shown in the examples above reflects the API response schema. For a shared type definition, extract it to a types file:

types/feedfusion.ts
ts
export type MediaType = "IMAGE" | "VIDEO" | "CAROUSEL_ALBUM";

export type Post = {
  id: string;
  mediaUrl: string;
  thumbnailUrl: string | null;
  permalink: string;
  caption: string;
  timestamp: string;
  mediaType: MediaType;
  likesCount: number;
  commentsCount: number;
};

export type FeedResponse = {
  data: Post[];
  pagination: {
    total: number;
    count: number;
    offset: number;
    hasMore: boolean;
  };
  meta: {
    feedId: string;
    lastSyncedAt: string;
    cacheTtl: number;
  };
};

Import this type wherever you call the API to keep the implementation type-safe across the project.

Using the @feedfusion/react package

If you'd prefer a pre-built component, install the @feedfusion/react package. It includes a FeedGrid server component for App Router projects:

 
bash
npm install @feedfusion/react
app/instagram-page/page.tsx
tsx
import { FeedGrid } from "@feedfusion/react/server";

export default async function InstagramPage() {
  return (
    <FeedGrid
      feedId="feed_abc123"
      apiKey={process.env.FEEDFUSION_API_KEY!}
      columns={3}
      limit={12}
    />
  );
}

Props for FeedGrid:

PropTypeDefaultDescription
feedIdstringrequiredYour feed ID from the dashboard.
apiKeystringrequiredYour FeedFusion API key. Pass via environment variable — never hardcode.
columnsnumber3Number of columns. Accepts 1–6.
limitnumber12Number of posts to display.
gapstring"1rem"Gap between grid items. Any valid CSS value.
aspectRatiostring"1""1" for square, "4/5" for portrait, "auto" for original.
hoverEffectstring"none"Accepts "zoom", "fade", or "none".

Continue reading