FeedFusionDocs
Get started free

Next.js Integration

Add an Instagram feed to a Next.js App Router project with a single fetch in a server component. Your dashboard gives you a username and a publicKey; pass them as query parameters. ISR handles caching. No npm package, no Authorization header. Normal Next.js.

The integration is a fetch call. Your dashboard exposes the two values you need: an Instagram username and a publicKey (a 32-character hex string). The Server Component below shows the full pattern.

Fetch the feed

Drop the two values into env vars:

.env.local
bash
FEEDFUSION_USERNAME=your-instagram-handle
FEEDFUSION_PUBLIC_KEY=your-32-char-public-key-from-dashboard

Then fetch from a Server Component. ISR keeps the response cached for an hour between requests, which matches FeedFusion's own edge-cache window (Section 3).

app/instagram/page.tsx
tsx
type Post = {
  id: string
  timestamp: string
  permalink: string
  mediaType: 'IMAGE' | 'VIDEO' | 'CAROUSEL_ALBUM'
  mediaUrl: string
  thumbnailUrl: string | null
  caption: string | null
}

export default async function InstagramPage() {
  const res = await fetch(
    `https://www.feed-fusion.com/api/feeds/${process.env.FEEDFUSION_USERNAME}` +
      `?publicKey=${process.env.FEEDFUSION_PUBLIC_KEY}&limit=12`,
    { next: { revalidate: 3600 } },
  )

  if (!res.ok) {
    throw new Error(`FeedFusion fetch failed: ${res.status}`)
  }

  const { posts } = (await res.json()) as { posts: Post[] }

  return (
    <ul className="grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4">
      {posts.map((post) => (
        <li key={post.id}>
          <a href={post.permalink} target="_blank" rel="noopener noreferrer">
            <img
              src={post.thumbnailUrl ?? post.mediaUrl}
              alt={post.caption?.slice(0, 120) ?? 'Instagram post'}
              loading="lazy"
            />
          </a>
        </li>
      ))}
    </ul>
  )
}

Server + client split

If the grid needs client-side interactivity (hover state, a lightbox, click tracking), fetch in the Server Component and pass posts as props to a Client Component. The publicKey is safe to expose either way (it's public by design), but keeping the fetch server-side means ISR caches the response for you.

app/instagram/page.tsx
tsx
import { InstagramGrid } from './instagram-grid'
import type { Post } from '@/types/feedfusion'

export default async function InstagramPage() {
  const res = await fetch(
    `https://www.feed-fusion.com/api/feeds/${process.env.FEEDFUSION_USERNAME}` +
      `?publicKey=${process.env.FEEDFUSION_PUBLIC_KEY}&limit=24`,
    { next: { revalidate: 3600 } },
  )
  const { posts } = (await res.json()) as { posts: Post[] }
  return <InstagramGrid posts={posts} />
}

Caching with ISR

FeedFusion caches at the edge for one hour. The meta.cacheTtl field in the response tells you exactly how long. Setting next.revalidate below 3600 won't make the feed fresher; it just refetches the same cached payload more often. Match your revalidate to the edge cache TTL for the most efficient setup.

Tune the cadence to how often the connected Instagram account actually posts:

  • Daily posters: revalidate: 3600 (one hour) keeps the feed current.
  • Weekly posters: revalidate: 86400 (24 hours) keeps usage low without making the grid stale.
  • Multiple-times-a-day posters: revalidate: 1800 (30 min) is reasonable, but the upstream edge cache is still hourly, so you may serve the same payload twice.

TypeScript types

Extract the response types to a shared file so every callsite stays consistent:

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

export type Post = {
  id: string
  timestamp: string // ISO 8601
  permalink: string // Instagram URL
  mediaType: MediaType
  mediaUrl: string
  thumbnailUrl: string | null // null for IMAGE, populated for VIDEO/CAROUSEL
  caption: string | null
}

export type FeedMeta = {
  tier: 'free' | 'pro' | 'agency'
  total: number // total posts available before slice
  limit: number // posts returned in this response
  offset: number // starting index of this slice
  cacheTtl: number // edge cache TTL in seconds
}

export type FeedResponse = {
  username: string
  meta: FeedMeta
  posts: Post[]
}

Pagination and limits

Add &offset=N to step through the cached posts beyond the first page. limit accepts 1 to 100. The meta.total field tells you when there are more pages to load. See the API Reference for the full query-parameter surface.

Questions? hello@feed-fusion.com

Continue reading