diff --git a/README.md b/README.md index 6601a65..92bc295 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Requests are prerendered when **all** of the following are true: - The `User-Agent` matches a known bot/crawler (Googlebot, Bingbot, Twitterbot, GPTBot, ClaudeBot, etc.) — OR the URL contains `_escaped_fragment_` — OR the `X-Bufferbot` header is present -- The URL does not end with a static asset extension (`.js`, `.css`, `.png`, etc.) +- The URL does not end with a static asset extension (`.js`, `.css`, `.png`, `.woff2`, `.webp`, etc.) — matched case-insensitively Everything else passes through to your normal route handlers. diff --git a/index.js b/index.js index 8b92979..a15e0cb 100644 --- a/index.js +++ b/index.js @@ -32,7 +32,8 @@ internals.extensionsToIgnore = [ '.pdf', '.doc', '.txt', '.ico', '.rss', '.zip', '.mp3', '.rar', '.exe', '.wmv', '.avi', '.ppt', '.mpg', '.mpeg', '.tif', '.wav', '.mov', '.psd', '.ai', '.xls', '.mp4', '.m4a', '.swf', '.dat', - '.dmg', '.iso', '.flv', '.m4v', '.torrent', '.ttf', '.woff', '.svg' + '.dmg', '.iso', '.flv', '.m4v', '.torrent', '.ttf', '.woff', '.svg', + '.woff2', '.otf', '.eot', '.webp', '.avif', '.webmanifest' ]; internals.defaults = { @@ -49,7 +50,8 @@ function isBot(userAgent) { } function isStaticAsset(pathname) { - return internals.extensionsToIgnore.some((ext) => pathname.endsWith(ext)); + const lowerPathname = pathname.toLowerCase(); + return internals.extensionsToIgnore.some((ext) => lowerPathname.endsWith(ext)); } function shouldPrerender(request) { diff --git a/test/contract.test.js b/test/contract.test.js index cb58e78..a9e4213 100644 --- a/test/contract.test.js +++ b/test/contract.test.js @@ -47,6 +47,8 @@ async function createServer(options = {}) { await server.register({ plugin, options: { serviceUrl: `${MOCK_URL}/`, token: TOKEN, ...options } }); server.route({ method: 'GET', path: '/', handler: () => 'original' }); server.route({ method: 'GET', path: '/styles.css', handler: () => 'body{}' }); + server.route({ method: 'GET', path: '/fonts/inter.woff2', handler: () => 'font-bytes' }); + server.route({ method: 'GET', path: '/STYLES.CSS', handler: () => 'BODY{}' }); server.route({ method: 'GET', path: '/blog/{slug}', handler: () => 'original' }); await server.initialize(); return server; @@ -108,6 +110,22 @@ test('static asset with bot UA emits no outgoing request', async () => { assert.equal(recorded.length, 0); }); +test('font asset with bot UA emits no outgoing request', async () => { + const server = await createServer(); + await server.inject({ method: 'GET', url: '/fonts/inter.woff2', headers: { 'user-agent': BOT_UA } }); + + const recorded = await getRecorded(); + assert.equal(recorded.length, 0); +}); + +test('uppercase static asset with bot UA emits no outgoing request', async () => { + const server = await createServer(); + await server.inject({ method: 'GET', url: '/STYLES.CSS', headers: { 'user-agent': BOT_UA } }); + + const recorded = await getRecorded(); + assert.equal(recorded.length, 0); +}); + test('X-Prerender-Token header is omitted when token is not configured', async () => { const server = await createServer({ token: null }); await server.inject({ method: 'GET', url: '/', headers: { 'user-agent': BOT_UA } }); diff --git a/test/smoke.test.js b/test/smoke.test.js index bcb4ee4..5a201a0 100644 --- a/test/smoke.test.js +++ b/test/smoke.test.js @@ -29,6 +29,8 @@ async function createServer(options = {}) { await server.register({ plugin, options }); server.route({ method: 'GET', path: '/', handler: () => 'original' }); server.route({ method: 'GET', path: '/style.css', handler: () => 'body{}' }); + server.route({ method: 'GET', path: '/fonts/inter.woff2', handler: () => 'font-bytes' }); + server.route({ method: 'GET', path: '/STYLES.CSS', handler: () => 'BODY{}' }); await server.initialize(); return server; } @@ -64,6 +66,22 @@ test('static asset with bot UA is not prerendered', async () => { assert.equal(res.payload, 'body{}'); }); +test('font asset with bot UA is not prerendered', async () => { + mockFetch(200, PRERENDERED_HTML); + const server = await createServer(); + const res = await server.inject({ method: 'GET', url: '/fonts/inter.woff2', headers: { 'user-agent': BOT_UA } }); + assert.equal(res.statusCode, 200); + assert.equal(res.payload, 'font-bytes'); +}); + +test('uppercase static asset with bot UA is not prerendered', async () => { + mockFetch(200, PRERENDERED_HTML); + const server = await createServer(); + const res = await server.inject({ method: 'GET', url: '/STYLES.CSS', headers: { 'user-agent': BOT_UA } }); + assert.equal(res.statusCode, 200); + assert.equal(res.payload, 'BODY{}'); +}); + test('_escaped_fragment_ query triggers prerender for any user agent', async () => { mockFetch(200, PRERENDERED_HTML); const server = await createServer({ token: 'test-token' });