@saketsawrav/instagram-feed 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # @saketsawrav/instagram-feed
2
+
3
+ Drop-in Instagram feed for Next.js apps. Fetches recent posts and proxies images server-side — no third-party widgets, no client-side scraping, no API keys required.
4
+
5
+ ## Features
6
+
7
+ - Fetches posts via Instagram's public web profile API
8
+ - Server-side image proxy (avoids Instagram's cross-origin blocking)
9
+ - In-memory caching with configurable TTL (default: 1 hour)
10
+ - Ready-made Next.js route handler factories
11
+ - Framework-agnostic core — use the fetcher anywhere Node.js runs
12
+ - Zero runtime dependencies
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install @saketsawrav/instagram-feed
18
+ ```
19
+
20
+ ## Quick Start (Next.js)
21
+
22
+ ### 1. Create the feed API route
23
+
24
+ ```ts
25
+ // app/api/instagram/route.ts
26
+ import { createFeedHandler } from '@saketsawrav/instagram-feed/nextjs';
27
+
28
+ export const GET = createFeedHandler({ username: 'your_username' });
29
+ ```
30
+
31
+ ### 2. Create the image proxy route
32
+
33
+ ```ts
34
+ // app/api/instagram/image/route.ts
35
+ import { createImageProxyHandler } from '@saketsawrav/instagram-feed/nextjs';
36
+
37
+ export const GET = createImageProxyHandler({ username: 'your_username' });
38
+ ```
39
+
40
+ ### 3. Fetch posts from your component
41
+
42
+ ```tsx
43
+ 'use client';
44
+
45
+ import { useEffect, useState } from 'react';
46
+ import type { InstagramPost } from '@saketsawrav/instagram-feed';
47
+
48
+ export default function InstagramGrid() {
49
+ const [posts, setPosts] = useState<InstagramPost[]>([]);
50
+
51
+ useEffect(() => {
52
+ fetch('/api/instagram')
53
+ .then((res) => res.json())
54
+ .then(setPosts)
55
+ .catch(() => {});
56
+ }, []);
57
+
58
+ return (
59
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }}>
60
+ {posts.map((post) => (
61
+ <a
62
+ key={post.shortcode}
63
+ href={`https://www.instagram.com/p/${post.shortcode}/`}
64
+ target="_blank"
65
+ rel="noopener noreferrer"
66
+ >
67
+ <img
68
+ src={`/api/instagram/image?shortcode=${post.shortcode}`}
69
+ alt=""
70
+ style={{ width: '100%', aspectRatio: '1', objectFit: 'cover' }}
71
+ loading="lazy"
72
+ />
73
+ </a>
74
+ ))}
75
+ </div>
76
+ );
77
+ }
78
+ ```
79
+
80
+ ## Configuration
81
+
82
+ Both `createFeedHandler` and `createImageProxyHandler` accept the same options:
83
+
84
+ | Option | Type | Default | Description |
85
+ |--------------|----------|---------------|--------------------------------|
86
+ | `username` | `string` | **required** | Instagram username to fetch |
87
+ | `count` | `number` | `9` | Number of posts to return |
88
+ | `cacheTtlMs` | `number` | `3600000` (1h)| In-memory cache TTL in ms |
89
+ | `igAppId` | `string` | built-in | Instagram app ID for API calls |
90
+
91
+ ## Framework-Agnostic Usage
92
+
93
+ The core fetcher works in any Node.js environment:
94
+
95
+ ```ts
96
+ import { fetchInstagramPosts } from '@saketsawrav/instagram-feed';
97
+
98
+ const posts = await fetchInstagramPosts({ username: 'your_username', count: 6 });
99
+ console.log(posts);
100
+ // [{ shortcode: 'abc123', thumbnailUrl: 'https://...' }, ...]
101
+ ```
102
+
103
+ ### Available exports from `@saketsawrav/instagram-feed`
104
+
105
+ | Export | Description |
106
+ |-------------------------|--------------------------------------------------|
107
+ | `fetchInstagramPosts()` | Fetch posts for a username (returns `InstagramPost[]`) |
108
+ | `getInstagramImageUrl()`| Get the full-res image URL for a shortcode |
109
+ | `proxyInstagramImage()` | Fetch and stream an image from Instagram CDN |
110
+ | `InstagramPost` | Type: `{ shortcode: string; thumbnailUrl: string }` |
111
+ | `InstagramFeedOptions` | Type: configuration options |
112
+
113
+ ### Available exports from `@saketsawrav/instagram-feed/nextjs`
114
+
115
+ | Export | Description |
116
+ |-----------------------------|---------------------------------------------|
117
+ | `createFeedHandler()` | Returns a Next.js GET handler for the feed |
118
+ | `createImageProxyHandler()` | Returns a Next.js GET handler for image proxy |
119
+
120
+ ## Why server-side?
121
+
122
+ Instagram blocks cross-origin image requests from browsers. This package solves that by:
123
+
124
+ 1. Fetching post metadata server-side (no browser CORS issues)
125
+ 2. Proxying images through your own API route (your domain serves the images)
126
+ 3. Caching both metadata and image URLs in memory (one Instagram API call per TTL window)
127
+
128
+ ## License
129
+
130
+ MIT
@@ -0,0 +1,92 @@
1
+ // src/fetcher.ts
2
+ var DEFAULT_COUNT = 9;
3
+ var DEFAULT_CACHE_TTL_MS = 36e5;
4
+ var DEFAULT_IG_APP_ID = "936619743392459";
5
+ var USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36";
6
+ var cacheByUsername = /* @__PURE__ */ new Map();
7
+ function buildHeaders(username, igAppId) {
8
+ return {
9
+ "User-Agent": USER_AGENT,
10
+ "x-ig-app-id": igAppId,
11
+ Accept: "*/*",
12
+ "Sec-Fetch-Mode": "cors",
13
+ "Sec-Fetch-Site": "same-origin",
14
+ "Sec-Fetch-Dest": "empty",
15
+ Referer: `https://www.instagram.com/${username}/`
16
+ };
17
+ }
18
+ async function fetchFromInstagram(username, count, igAppId) {
19
+ const res = await fetch(
20
+ `https://www.instagram.com/api/v1/users/web_profile_info/?username=${username}`,
21
+ { headers: buildHeaders(username, igAppId) }
22
+ );
23
+ if (!res.ok) {
24
+ return { posts: [], imageMap: {}, timestamp: Date.now() };
25
+ }
26
+ const json = await res.json();
27
+ const edges = json?.data?.user?.edge_owner_to_timeline_media?.edges ?? [];
28
+ const posts = [];
29
+ const imageMap = {};
30
+ for (const edge of edges) {
31
+ const node = edge?.node;
32
+ if (!node?.shortcode) continue;
33
+ const thumbnailUrl = node.thumbnail_src ?? node.display_url ?? "";
34
+ const displayUrl = node.display_url ?? node.thumbnail_src ?? "";
35
+ imageMap[node.shortcode] = displayUrl;
36
+ if (posts.length < count) {
37
+ posts.push({ shortcode: node.shortcode, thumbnailUrl });
38
+ }
39
+ }
40
+ return { posts, imageMap, timestamp: Date.now() };
41
+ }
42
+ function getCached(username, cacheTtlMs) {
43
+ const entry = cacheByUsername.get(username);
44
+ if (entry && Date.now() - entry.timestamp < cacheTtlMs) return entry;
45
+ return null;
46
+ }
47
+ async function ensureCache(opts) {
48
+ const existing = getCached(opts.username, opts.cacheTtlMs);
49
+ if (existing) return existing;
50
+ const fresh = await fetchFromInstagram(opts.username, opts.count, opts.igAppId);
51
+ if (fresh.posts.length > 0) {
52
+ cacheByUsername.set(opts.username, fresh);
53
+ }
54
+ return fresh;
55
+ }
56
+ function resolveOptions(opts) {
57
+ return {
58
+ username: opts.username,
59
+ count: opts.count ?? DEFAULT_COUNT,
60
+ cacheTtlMs: opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,
61
+ igAppId: opts.igAppId ?? DEFAULT_IG_APP_ID
62
+ };
63
+ }
64
+ async function fetchInstagramPosts(opts) {
65
+ const resolved = resolveOptions(opts);
66
+ const cached = await ensureCache(resolved);
67
+ return cached.posts;
68
+ }
69
+ async function getInstagramImageUrl(opts, shortcode) {
70
+ const resolved = resolveOptions(opts);
71
+ const cached = await ensureCache(resolved);
72
+ return cached.imageMap[shortcode] ?? null;
73
+ }
74
+ async function proxyInstagramImage(imageUrl) {
75
+ const res = await fetch(imageUrl, {
76
+ headers: {
77
+ "User-Agent": USER_AGENT,
78
+ Referer: "https://www.instagram.com/"
79
+ }
80
+ });
81
+ if (!res.ok || !res.body) return null;
82
+ return {
83
+ body: res.body,
84
+ contentType: res.headers.get("content-type") ?? "image/jpeg"
85
+ };
86
+ }
87
+
88
+ export {
89
+ fetchInstagramPosts,
90
+ getInstagramImageUrl,
91
+ proxyInstagramImage
92
+ };
@@ -0,0 +1,10 @@
1
+ import { I as InstagramFeedOptions, a as InstagramPost } from './types-CJuNiTch.mjs';
2
+
3
+ declare function fetchInstagramPosts(opts: InstagramFeedOptions): Promise<InstagramPost[]>;
4
+ declare function getInstagramImageUrl(opts: InstagramFeedOptions, shortcode: string): Promise<string | null>;
5
+ declare function proxyInstagramImage(imageUrl: string): Promise<{
6
+ body: ReadableStream<Uint8Array>;
7
+ contentType: string;
8
+ } | null>;
9
+
10
+ export { InstagramFeedOptions, InstagramPost, fetchInstagramPosts, getInstagramImageUrl, proxyInstagramImage };
@@ -0,0 +1,10 @@
1
+ import { I as InstagramFeedOptions, a as InstagramPost } from './types-CJuNiTch.js';
2
+
3
+ declare function fetchInstagramPosts(opts: InstagramFeedOptions): Promise<InstagramPost[]>;
4
+ declare function getInstagramImageUrl(opts: InstagramFeedOptions, shortcode: string): Promise<string | null>;
5
+ declare function proxyInstagramImage(imageUrl: string): Promise<{
6
+ body: ReadableStream<Uint8Array>;
7
+ contentType: string;
8
+ } | null>;
9
+
10
+ export { InstagramFeedOptions, InstagramPost, fetchInstagramPosts, getInstagramImageUrl, proxyInstagramImage };
package/dist/index.js ADDED
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ fetchInstagramPosts: () => fetchInstagramPosts,
24
+ getInstagramImageUrl: () => getInstagramImageUrl,
25
+ proxyInstagramImage: () => proxyInstagramImage
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/fetcher.ts
30
+ var DEFAULT_COUNT = 9;
31
+ var DEFAULT_CACHE_TTL_MS = 36e5;
32
+ var DEFAULT_IG_APP_ID = "936619743392459";
33
+ var USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36";
34
+ var cacheByUsername = /* @__PURE__ */ new Map();
35
+ function buildHeaders(username, igAppId) {
36
+ return {
37
+ "User-Agent": USER_AGENT,
38
+ "x-ig-app-id": igAppId,
39
+ Accept: "*/*",
40
+ "Sec-Fetch-Mode": "cors",
41
+ "Sec-Fetch-Site": "same-origin",
42
+ "Sec-Fetch-Dest": "empty",
43
+ Referer: `https://www.instagram.com/${username}/`
44
+ };
45
+ }
46
+ async function fetchFromInstagram(username, count, igAppId) {
47
+ const res = await fetch(
48
+ `https://www.instagram.com/api/v1/users/web_profile_info/?username=${username}`,
49
+ { headers: buildHeaders(username, igAppId) }
50
+ );
51
+ if (!res.ok) {
52
+ return { posts: [], imageMap: {}, timestamp: Date.now() };
53
+ }
54
+ const json = await res.json();
55
+ const edges = json?.data?.user?.edge_owner_to_timeline_media?.edges ?? [];
56
+ const posts = [];
57
+ const imageMap = {};
58
+ for (const edge of edges) {
59
+ const node = edge?.node;
60
+ if (!node?.shortcode) continue;
61
+ const thumbnailUrl = node.thumbnail_src ?? node.display_url ?? "";
62
+ const displayUrl = node.display_url ?? node.thumbnail_src ?? "";
63
+ imageMap[node.shortcode] = displayUrl;
64
+ if (posts.length < count) {
65
+ posts.push({ shortcode: node.shortcode, thumbnailUrl });
66
+ }
67
+ }
68
+ return { posts, imageMap, timestamp: Date.now() };
69
+ }
70
+ function getCached(username, cacheTtlMs) {
71
+ const entry = cacheByUsername.get(username);
72
+ if (entry && Date.now() - entry.timestamp < cacheTtlMs) return entry;
73
+ return null;
74
+ }
75
+ async function ensureCache(opts) {
76
+ const existing = getCached(opts.username, opts.cacheTtlMs);
77
+ if (existing) return existing;
78
+ const fresh = await fetchFromInstagram(opts.username, opts.count, opts.igAppId);
79
+ if (fresh.posts.length > 0) {
80
+ cacheByUsername.set(opts.username, fresh);
81
+ }
82
+ return fresh;
83
+ }
84
+ function resolveOptions(opts) {
85
+ return {
86
+ username: opts.username,
87
+ count: opts.count ?? DEFAULT_COUNT,
88
+ cacheTtlMs: opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,
89
+ igAppId: opts.igAppId ?? DEFAULT_IG_APP_ID
90
+ };
91
+ }
92
+ async function fetchInstagramPosts(opts) {
93
+ const resolved = resolveOptions(opts);
94
+ const cached = await ensureCache(resolved);
95
+ return cached.posts;
96
+ }
97
+ async function getInstagramImageUrl(opts, shortcode) {
98
+ const resolved = resolveOptions(opts);
99
+ const cached = await ensureCache(resolved);
100
+ return cached.imageMap[shortcode] ?? null;
101
+ }
102
+ async function proxyInstagramImage(imageUrl) {
103
+ const res = await fetch(imageUrl, {
104
+ headers: {
105
+ "User-Agent": USER_AGENT,
106
+ Referer: "https://www.instagram.com/"
107
+ }
108
+ });
109
+ if (!res.ok || !res.body) return null;
110
+ return {
111
+ body: res.body,
112
+ contentType: res.headers.get("content-type") ?? "image/jpeg"
113
+ };
114
+ }
115
+ // Annotate the CommonJS export names for ESM import in node:
116
+ 0 && (module.exports = {
117
+ fetchInstagramPosts,
118
+ getInstagramImageUrl,
119
+ proxyInstagramImage
120
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,10 @@
1
+ import {
2
+ fetchInstagramPosts,
3
+ getInstagramImageUrl,
4
+ proxyInstagramImage
5
+ } from "./chunk-QEYBYUKH.mjs";
6
+ export {
7
+ fetchInstagramPosts,
8
+ getInstagramImageUrl,
9
+ proxyInstagramImage
10
+ };
@@ -0,0 +1,7 @@
1
+ import { I as InstagramFeedOptions, a as InstagramPost } from './types-CJuNiTch.mjs';
2
+ import { NextResponse, NextRequest } from 'next/server';
3
+
4
+ declare function createFeedHandler(opts: InstagramFeedOptions): () => Promise<NextResponse<InstagramPost[]>>;
5
+ declare function createImageProxyHandler(opts: InstagramFeedOptions): (request: NextRequest) => Promise<NextResponse<unknown>>;
6
+
7
+ export { InstagramFeedOptions, InstagramPost, createFeedHandler, createImageProxyHandler };
@@ -0,0 +1,7 @@
1
+ import { I as InstagramFeedOptions, a as InstagramPost } from './types-CJuNiTch.js';
2
+ import { NextResponse, NextRequest } from 'next/server';
3
+
4
+ declare function createFeedHandler(opts: InstagramFeedOptions): () => Promise<NextResponse<InstagramPost[]>>;
5
+ declare function createImageProxyHandler(opts: InstagramFeedOptions): (request: NextRequest) => Promise<NextResponse<unknown>>;
6
+
7
+ export { InstagramFeedOptions, InstagramPost, createFeedHandler, createImageProxyHandler };
package/dist/nextjs.js ADDED
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/nextjs.ts
21
+ var nextjs_exports = {};
22
+ __export(nextjs_exports, {
23
+ createFeedHandler: () => createFeedHandler,
24
+ createImageProxyHandler: () => createImageProxyHandler
25
+ });
26
+ module.exports = __toCommonJS(nextjs_exports);
27
+
28
+ // src/handlers.ts
29
+ var import_server = require("next/server");
30
+
31
+ // src/fetcher.ts
32
+ var DEFAULT_COUNT = 9;
33
+ var DEFAULT_CACHE_TTL_MS = 36e5;
34
+ var DEFAULT_IG_APP_ID = "936619743392459";
35
+ var USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36";
36
+ var cacheByUsername = /* @__PURE__ */ new Map();
37
+ function buildHeaders(username, igAppId) {
38
+ return {
39
+ "User-Agent": USER_AGENT,
40
+ "x-ig-app-id": igAppId,
41
+ Accept: "*/*",
42
+ "Sec-Fetch-Mode": "cors",
43
+ "Sec-Fetch-Site": "same-origin",
44
+ "Sec-Fetch-Dest": "empty",
45
+ Referer: `https://www.instagram.com/${username}/`
46
+ };
47
+ }
48
+ async function fetchFromInstagram(username, count, igAppId) {
49
+ const res = await fetch(
50
+ `https://www.instagram.com/api/v1/users/web_profile_info/?username=${username}`,
51
+ { headers: buildHeaders(username, igAppId) }
52
+ );
53
+ if (!res.ok) {
54
+ return { posts: [], imageMap: {}, timestamp: Date.now() };
55
+ }
56
+ const json = await res.json();
57
+ const edges = json?.data?.user?.edge_owner_to_timeline_media?.edges ?? [];
58
+ const posts = [];
59
+ const imageMap = {};
60
+ for (const edge of edges) {
61
+ const node = edge?.node;
62
+ if (!node?.shortcode) continue;
63
+ const thumbnailUrl = node.thumbnail_src ?? node.display_url ?? "";
64
+ const displayUrl = node.display_url ?? node.thumbnail_src ?? "";
65
+ imageMap[node.shortcode] = displayUrl;
66
+ if (posts.length < count) {
67
+ posts.push({ shortcode: node.shortcode, thumbnailUrl });
68
+ }
69
+ }
70
+ return { posts, imageMap, timestamp: Date.now() };
71
+ }
72
+ function getCached(username, cacheTtlMs) {
73
+ const entry = cacheByUsername.get(username);
74
+ if (entry && Date.now() - entry.timestamp < cacheTtlMs) return entry;
75
+ return null;
76
+ }
77
+ async function ensureCache(opts) {
78
+ const existing = getCached(opts.username, opts.cacheTtlMs);
79
+ if (existing) return existing;
80
+ const fresh = await fetchFromInstagram(opts.username, opts.count, opts.igAppId);
81
+ if (fresh.posts.length > 0) {
82
+ cacheByUsername.set(opts.username, fresh);
83
+ }
84
+ return fresh;
85
+ }
86
+ function resolveOptions(opts) {
87
+ return {
88
+ username: opts.username,
89
+ count: opts.count ?? DEFAULT_COUNT,
90
+ cacheTtlMs: opts.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,
91
+ igAppId: opts.igAppId ?? DEFAULT_IG_APP_ID
92
+ };
93
+ }
94
+ async function fetchInstagramPosts(opts) {
95
+ const resolved = resolveOptions(opts);
96
+ const cached = await ensureCache(resolved);
97
+ return cached.posts;
98
+ }
99
+ async function getInstagramImageUrl(opts, shortcode) {
100
+ const resolved = resolveOptions(opts);
101
+ const cached = await ensureCache(resolved);
102
+ return cached.imageMap[shortcode] ?? null;
103
+ }
104
+ async function proxyInstagramImage(imageUrl) {
105
+ const res = await fetch(imageUrl, {
106
+ headers: {
107
+ "User-Agent": USER_AGENT,
108
+ Referer: "https://www.instagram.com/"
109
+ }
110
+ });
111
+ if (!res.ok || !res.body) return null;
112
+ return {
113
+ body: res.body,
114
+ contentType: res.headers.get("content-type") ?? "image/jpeg"
115
+ };
116
+ }
117
+
118
+ // src/handlers.ts
119
+ function createFeedHandler(opts) {
120
+ return async function GET() {
121
+ const posts = await fetchInstagramPosts(opts);
122
+ return import_server.NextResponse.json(posts, {
123
+ headers: {
124
+ "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=7200"
125
+ }
126
+ });
127
+ };
128
+ }
129
+ function createImageProxyHandler(opts) {
130
+ return async function GET(request) {
131
+ const shortcode = request.nextUrl.searchParams.get("shortcode");
132
+ if (!shortcode) {
133
+ return new import_server.NextResponse("Missing shortcode", { status: 400 });
134
+ }
135
+ const imageUrl = await getInstagramImageUrl(opts, shortcode);
136
+ if (!imageUrl) {
137
+ return new import_server.NextResponse("Not found", { status: 404 });
138
+ }
139
+ const result = await proxyInstagramImage(imageUrl);
140
+ if (!result) {
141
+ return new import_server.NextResponse("Image fetch failed", { status: 502 });
142
+ }
143
+ return new import_server.NextResponse(result.body, {
144
+ headers: {
145
+ "Content-Type": result.contentType,
146
+ "Cache-Control": "public, max-age=86400, s-maxage=86400"
147
+ }
148
+ });
149
+ };
150
+ }
151
+ // Annotate the CommonJS export names for ESM import in node:
152
+ 0 && (module.exports = {
153
+ createFeedHandler,
154
+ createImageProxyHandler
155
+ });
@@ -0,0 +1,44 @@
1
+ import {
2
+ fetchInstagramPosts,
3
+ getInstagramImageUrl,
4
+ proxyInstagramImage
5
+ } from "./chunk-QEYBYUKH.mjs";
6
+
7
+ // src/handlers.ts
8
+ import { NextResponse } from "next/server";
9
+ function createFeedHandler(opts) {
10
+ return async function GET() {
11
+ const posts = await fetchInstagramPosts(opts);
12
+ return NextResponse.json(posts, {
13
+ headers: {
14
+ "Cache-Control": "public, s-maxage=3600, stale-while-revalidate=7200"
15
+ }
16
+ });
17
+ };
18
+ }
19
+ function createImageProxyHandler(opts) {
20
+ return async function GET(request) {
21
+ const shortcode = request.nextUrl.searchParams.get("shortcode");
22
+ if (!shortcode) {
23
+ return new NextResponse("Missing shortcode", { status: 400 });
24
+ }
25
+ const imageUrl = await getInstagramImageUrl(opts, shortcode);
26
+ if (!imageUrl) {
27
+ return new NextResponse("Not found", { status: 404 });
28
+ }
29
+ const result = await proxyInstagramImage(imageUrl);
30
+ if (!result) {
31
+ return new NextResponse("Image fetch failed", { status: 502 });
32
+ }
33
+ return new NextResponse(result.body, {
34
+ headers: {
35
+ "Content-Type": result.contentType,
36
+ "Cache-Control": "public, max-age=86400, s-maxage=86400"
37
+ }
38
+ });
39
+ };
40
+ }
41
+ export {
42
+ createFeedHandler,
43
+ createImageProxyHandler
44
+ };
@@ -0,0 +1,12 @@
1
+ interface InstagramPost {
2
+ shortcode: string;
3
+ thumbnailUrl: string;
4
+ }
5
+ interface InstagramFeedOptions {
6
+ username: string;
7
+ count?: number;
8
+ cacheTtlMs?: number;
9
+ igAppId?: string;
10
+ }
11
+
12
+ export type { InstagramFeedOptions as I, InstagramPost as a };
@@ -0,0 +1,12 @@
1
+ interface InstagramPost {
2
+ shortcode: string;
3
+ thumbnailUrl: string;
4
+ }
5
+ interface InstagramFeedOptions {
6
+ username: string;
7
+ count?: number;
8
+ cacheTtlMs?: number;
9
+ igAppId?: string;
10
+ }
11
+
12
+ export type { InstagramFeedOptions as I, InstagramPost as a };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@saketsawrav/instagram-feed",
3
+ "version": "0.1.0",
4
+ "description": "Drop-in Instagram feed for Next.js — fetches posts and proxies images server-side, no third-party widgets",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./nextjs": {
15
+ "types": "./dist/nextjs.d.ts",
16
+ "import": "./dist/nextjs.mjs",
17
+ "require": "./dist/nextjs.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup src/index.ts src/nextjs.ts --format cjs,esm --dts",
25
+ "dev": "tsup src/index.ts src/nextjs.ts --format cjs,esm --dts --watch",
26
+ "lint": "tsc --noEmit",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "peerDependencies": {
30
+ "next": ">=13.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "next": "^14.0.0",
34
+ "tsup": "^8.0.0",
35
+ "typescript": "^5.0.0"
36
+ },
37
+ "keywords": [
38
+ "instagram",
39
+ "nextjs",
40
+ "feed",
41
+ "embed",
42
+ "image-proxy"
43
+ ],
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/lifesciencetrust/nextjs-instagram-feed"
48
+ }
49
+ }