diff --git a/nginx/mime.types b/nginx/mime.types index b7ff2b4..45bd9e0 100644 --- a/nginx/mime.types +++ b/nginx/mime.types @@ -29,6 +29,7 @@ types { application/java-archive jar war ear; application/json json; + application/manifest+json webmanifest; application/mac-binhex40 hqx; application/msword doc; application/pdf pdf; diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 3522f47..4654753 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -118,13 +118,13 @@ http { location ~* \.(?:css|js|jpg|svg)$ { gzip_static on; - try_files $uri $uri/ $uri.html /iframe.html /index.html; + try_files $uri =404; } location ~* \.(?:json)$ { gzip_static on; - try_files $uri $uri/ $uri.html /iframe.html /index.html; + try_files $uri =404; } location ~* (?:#.*#|\.(?:bak|conf|dist|fla|in[ci]|log|orig|psd|sh|sql|sw[op])|~)$ { diff --git a/nginx/tests/mime.types b/nginx/tests/mime.types index b7ff2b4..45bd9e0 100644 --- a/nginx/tests/mime.types +++ b/nginx/tests/mime.types @@ -29,6 +29,7 @@ types { application/java-archive jar war ear; application/json json; + application/manifest+json webmanifest; application/mac-binhex40 hqx; application/msword doc; application/pdf pdf; diff --git a/nginx/tests/nginx.conf b/nginx/tests/nginx.conf index 1749013..9c8d8e7 100644 --- a/nginx/tests/nginx.conf +++ b/nginx/tests/nginx.conf @@ -113,14 +113,14 @@ http { location ~* \.(?:css|js|jpg|svg)$ { gzip_static on; - try_files $uri $uri/ $uri.html /iframe.html /index.html; + try_files $uri =404; expires max; } location ~* \.(?:json)$ { gzip_static on; - try_files $uri $uri/ $uri.html /iframe.html /index.html; + try_files $uri =404; expires max; } diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png deleted file mode 100644 index f938b9b..0000000 Binary files a/public/favicon-16x16.png and /dev/null differ diff --git a/public/favicon.ico b/public/favicon.ico index e1c5350..e4290fb 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000..303c7d0 --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/src/app/_layout/metadata.tsx b/src/app/_layout/metadata.tsx index c3f1316..2f9c008 100644 --- a/src/app/_layout/metadata.tsx +++ b/src/app/_layout/metadata.tsx @@ -75,15 +75,17 @@ const buildMetadata = (locale: Locale): Metadata => ({ creator: '@raferdev', }, icons: { - icon: '/favicon.ico', - shortcut: '/favicon-16x16.png', + icon: [ + { url: '/favicon.ico', sizes: '32x32' }, + { url: '/icon.svg', type: 'image/svg+xml' }, + ], apple: '/apple-touch-icon.png', }, robots: { index: true, follow: true, }, - manifest: `${meta.url}/site.webmanifest`, + manifest: '/manifest.webmanifest', }) const viewport: Viewport = { diff --git a/tests/icons.spec.ts b/tests/icons.spec.ts new file mode 100644 index 0000000..820470a --- /dev/null +++ b/tests/icons.spec.ts @@ -0,0 +1,76 @@ +import { expect, test } from '@playwright/test' + +const PAGES = ['/', '/pt'] + +test.describe('Icons and manifest', () => { + for (const path of PAGES) { + test(`Expected - Every icon and manifest linked from ${path} to load`, async ({ + page, + request, + }) => { + await page.goto(path) + + const links = await page + .locator( + 'head link[rel~="icon"], head link[rel="apple-touch-icon"], head link[rel="manifest"]' + ) + .evaluateAll((elements) => + elements.map((element) => ({ + rel: element.getAttribute('rel'), + type: element.getAttribute('type'), + href: (element as HTMLLinkElement).href, + })) + ) + + expect(links.map(({ rel }) => rel)).toEqual( + expect.arrayContaining(['icon', 'apple-touch-icon', 'manifest']) + ) + + for (const { rel, type, href } of links) { + const response = await request.get(href) + const contentType = response.headers()['content-type'] + expect(response.status(), `${rel} ${href}`).toBe(200) + if (rel !== 'manifest') expect(contentType, href).toMatch(/^image\//) + if (type) expect(contentType).toContain(type) + + if (rel === 'manifest') { + expect(contentType).toContain('application/manifest+json') + const manifest = await response.json() + for (const icon of manifest.icons) { + const iconResponse = await request.get(new URL(icon.src, href).href) + expect(iconResponse.status(), icon.src).toBe(200) + expect(iconResponse.headers()['content-type'], icon.src).toMatch( + /^image\// + ) + } + } + } + }) + } + + for (const asset of [ + '/missing.svg', + '/missing.js', + '/missing.css', + '/missing.json', + ]) { + test(`Expected - A missing ${asset} to 404 instead of serving a page`, async ({ + request, + }) => { + const response = await request.get(asset) + + expect(response.status()).toBe(404) + }) + } + + test('Expected - The SVG icon to follow the color scheme', async ({ + request, + }) => { + const response = await request.get('/icon.svg') + const svg = await response.text() + + expect(response.headers()['content-type']).toContain('image/svg+xml') + expect(svg.startsWith('