Skip to content

Commit 4b2f298

Browse files
committed
fix: Added prerender step for cloudflare.
1 parent ab7b6f1 commit 4b2f298

File tree

4 files changed

+87
-47
lines changed

4 files changed

+87
-47
lines changed

src/__tests__/pages/api/__tests__/[version]/icons/index.test.ts

Lines changed: 26 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { GET } from '../../../../../../pages/api/[version]/icons/index'
2-
import { getAllIcons } from '../../../../../../utils/icons/reactIcons'
32

43
const mockApiIndex = {
54
versions: ['v5', 'v6'],
@@ -35,8 +34,18 @@ const mockIcons = [
3534
},
3635
]
3736

37+
function createFetchMock(): typeof fetch {
38+
return jest.fn((input: RequestInfo | URL) => {
39+
const url = typeof input === 'string' ? input : input.toString()
40+
const json = () =>
41+
Promise.resolve(
42+
url.includes('iconsIndex.json') ? { icons: mockIcons } : mockApiIndex
43+
)
44+
return Promise.resolve({ ok: true, json } as Response)
45+
}) as typeof fetch
46+
}
47+
3848
jest.mock('../../../../../../utils/icons/reactIcons', () => ({
39-
getAllIcons: jest.fn(() => Promise.resolve(mockIcons)),
4049
filterIcons: jest.fn((icons: typeof mockIcons, filter: string) => {
4150
if (!filter || !filter.trim()) {
4251
return icons
@@ -51,12 +60,7 @@ jest.mock('../../../../../../utils/icons/reactIcons', () => ({
5160
}))
5261

5362
it('returns all icons with metadata', async () => {
54-
global.fetch = jest.fn(() =>
55-
Promise.resolve({
56-
ok: true,
57-
json: () => Promise.resolve(mockApiIndex),
58-
} as Response),
59-
)
63+
global.fetch = createFetchMock()
6064

6165
const response = await GET({
6266
params: { version: 'v6' },
@@ -83,12 +87,7 @@ it('returns all icons with metadata', async () => {
8387
})
8488

8589
it('filters icons when filter parameter is provided', async () => {
86-
global.fetch = jest.fn(() =>
87-
Promise.resolve({
88-
ok: true,
89-
json: () => Promise.resolve(mockApiIndex),
90-
} as Response),
91-
)
90+
global.fetch = createFetchMock()
9291

9392
const response = await GET({
9493
params: { version: 'v6' },
@@ -106,12 +105,7 @@ it('filters icons when filter parameter is provided', async () => {
106105
})
107106

108107
it('filter is case-insensitive', async () => {
109-
global.fetch = jest.fn(() =>
110-
Promise.resolve({
111-
ok: true,
112-
json: () => Promise.resolve(mockApiIndex),
113-
} as Response),
114-
)
108+
global.fetch = createFetchMock()
115109

116110
const response = await GET({
117111
params: { version: 'v6' },
@@ -126,12 +120,7 @@ it('filter is case-insensitive', async () => {
126120
})
127121

128122
it('returns empty icons array when filter yields no matches', async () => {
129-
global.fetch = jest.fn(() =>
130-
Promise.resolve({
131-
ok: true,
132-
json: () => Promise.resolve(mockApiIndex),
133-
} as Response),
134-
)
123+
global.fetch = createFetchMock()
135124

136125
const response = await GET({
137126
params: { version: 'v6' },
@@ -147,12 +136,7 @@ it('returns empty icons array when filter yields no matches', async () => {
147136
})
148137

149138
it('returns 404 error for nonexistent version', async () => {
150-
global.fetch = jest.fn(() =>
151-
Promise.resolve({
152-
ok: true,
153-
json: () => Promise.resolve(mockApiIndex),
154-
} as Response),
155-
)
139+
global.fetch = createFetchMock()
156140

157141
const response = await GET({
158142
params: { version: 'v99' },
@@ -169,12 +153,7 @@ it('returns 404 error for nonexistent version', async () => {
169153
})
170154

171155
it('returns 400 error when version parameter is missing', async () => {
172-
global.fetch = jest.fn(() =>
173-
Promise.resolve({
174-
ok: true,
175-
json: () => Promise.resolve(mockApiIndex),
176-
} as Response),
177-
)
156+
global.fetch = createFetchMock()
178157

179158
const response = await GET({
180159
params: {},
@@ -189,15 +168,17 @@ it('returns 400 error when version parameter is missing', async () => {
189168
jest.restoreAllMocks()
190169
})
191170

192-
it('returns 500 error when getAllIcons throws', async () => {
193-
;(getAllIcons as jest.Mock).mockRejectedValueOnce(new Error('Load failed'))
194-
195-
global.fetch = jest.fn(() =>
196-
Promise.resolve({
171+
it('returns 500 error when fetchIconsIndex throws', async () => {
172+
global.fetch = jest.fn((input: RequestInfo | URL) => {
173+
const url = typeof input === 'string' ? input : input.toString()
174+
if (url.includes('iconsIndex.json')) {
175+
return Promise.resolve({ ok: false, status: 500, statusText: 'Internal Server Error' } as Response)
176+
}
177+
return Promise.resolve({
197178
ok: true,
198179
json: () => Promise.resolve(mockApiIndex),
199-
} as Response),
200-
)
180+
} as Response)
181+
}) as typeof fetch
201182

202183
const response = await GET({
203184
params: { version: 'v6' },

src/pages/api/[version]/icons/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { APIRoute } from 'astro'
22
import { createJsonResponse } from '../../../../utils/apiHelpers'
33
import { fetchApiIndex } from '../../../../utils/apiIndex/fetch'
4-
import { getAllIcons, filterIcons } from '../../../../utils/icons/reactIcons'
4+
import { fetchIconsIndex } from '../../../../utils/icons/fetch'
5+
import { filterIcons } from '../../../../utils/icons/reactIcons'
56

67
export const prerender = false
78

@@ -29,7 +30,7 @@ export const GET: APIRoute = async ({ params, url }) => {
2930
}
3031

3132
const filter = url.searchParams.get('filter') ?? ''
32-
const icons = await getAllIcons()
33+
const icons = await fetchIconsIndex(url)
3334
const filtered = filterIcons(icons, filter)
3435

3536
return createJsonResponse({

src/pages/iconsIndex.json.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { APIRoute } from 'astro'
2+
import { getAllIcons } from '../utils/icons/reactIcons'
3+
4+
/**
5+
* Prerender at build time so this doesn't run in the Cloudflare Worker.
6+
* getAllIcons() uses dynamic imports of react-icons which fail in Workers
7+
* due to bundle size and Node.js compatibility.
8+
*/
9+
export const prerender = true
10+
11+
export const GET: APIRoute = async () => {
12+
try {
13+
const icons = await getAllIcons()
14+
return new Response(JSON.stringify({ icons }), {
15+
status: 200,
16+
headers: {
17+
'Content-Type': 'application/json',
18+
},
19+
})
20+
} catch (error) {
21+
return new Response(
22+
JSON.stringify({ error: 'Failed to load icons index', details: String(error) }),
23+
{
24+
status: 500,
25+
headers: {
26+
'Content-Type': 'application/json',
27+
},
28+
}
29+
)
30+
}
31+
}

src/utils/icons/fetch.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { IconMetadata } from './reactIcons'
2+
3+
export interface IconsIndex {
4+
icons: IconMetadata[]
5+
}
6+
7+
/**
8+
* Fetches the icons index from the server as a static asset.
9+
* Used by API routes at runtime instead of calling getAllIcons() which uses
10+
* dynamic imports that fail in Cloudflare Workers.
11+
*
12+
* @param url - The URL object from the API route context
13+
* @returns Promise resolving to the icons index structure
14+
*/
15+
export async function fetchIconsIndex(url: URL): Promise<IconMetadata[]> {
16+
const iconsIndexUrl = new URL('/iconsIndex.json', url.origin)
17+
const response = await fetch(iconsIndexUrl)
18+
19+
if (!response.ok) {
20+
throw new Error(
21+
`Failed to load icons index: ${response.status} ${response.statusText}`
22+
)
23+
}
24+
25+
const data = (await response.json()) as IconsIndex
26+
return data.icons
27+
}

0 commit comments

Comments
 (0)