learning-website-nextjs1-8 Exercise 2: Downloads on Course Pages, Dead Links, and Do Images and Fonts Need Optimising? ================================================================================================================================ PART A: the downloads. A course folder's page lists its PDFs, and its solution files (folded away in a
), taken from what was really copied, so a PDF that does not exist cannot be linked from here. The listing component gains an optional "assets" list (in packages/ui/src/Navigation.tsx): {assets && (assets.pdfs.length > 0 || assets.solutions.length > 0) ? ( <>

Downloads

PDFs as links
Exercise solutions (N files) ...
) : null} and the page asks for them with listAssets(join(process.cwd(), "public"), folder) (in apps/languages/app/[...path]/page.tsx). /hungary/hungarian-basic-3 shows: Downloads: Hungarian_Basic_Conversation_3_Course.pdf, hungarian_basic_conversation_3_1.pdf, ... /hungary (no PDFs of its own) shows no Downloads heading. The PDF address answers 200, application/pdf. PART B: do the pages link to files that exist? check-assets.mjs prepares every page and checks every link to a .pdf or .txt against the files a build would copy: Save as check-assets.mjs: // Does every PDF and solution file that a page links to exist? Checked against the files the builds would copy. // node check-assets.mjs (LW_CONTENT_ROOT names the content folder) import { assetPaths, loadPages, prepareFragment, readContentFile } from "./packages/content/src/index.ts"; import { unquote } from "./packages/content/src/index.ts"; const root = process.env.LW_CONTENT_ROOT; if (!root) throw new Error("Set LW_CONTENT_ROOT"); const have = new Set(assetPaths(root)); const { pages } = loadPages(root); let checked = 0; const missing = []; const bySite = {}; for (const page of pages) { const fragment = prepareFragment(readContentFile(root, page.path), page.path, page.site); for (const match of fragment.matchAll(/href=(["'])(\/[^"'#?]*\.(?:pdf|txt))\1/gi)) { checked++; const target = unquote(match[2]).replace(/^\//, ""); if (!have.has(target)) { missing.push([page.path, match[2]]); bySite[page.site] = (bySite[page.site] ?? 0) + 1; } } } console.log(`${have.size} files exist; ${checked} links to them from ${pages.length} pages; ${missing.length} point at a file that does not exist`); console.log("missing, by site:", JSON.stringify(bySite)); const folders = {}; for (const [, link] of missing) { const k = link.split("/").slice(1, 5).join("/"); folders[k] = (folders[k] ?? 0) + 1; } for (const [k, n] of Object.entries(folders).sort((a, b) => b[1] - a[1]).slice(0, 6)) console.log(` ${String(n).padStart(4)} /${k}/...`); console.log("example:", missing[0]?.join(" -> ")); 8431 files exist; 11188 links to them from 4419 pages; 178 point at a file that does not exist missing, by site: {"creative":3,"systems":1,"webdevelopment":174} 54 /web-development/scripting-and-backend/javascript/fundamentals/... 24 /web-development/scripting-and-backend/express-js/fundamentals/... 24 /web-development/scripting-and-backend/express-js/intermediate-advanced/... 24 /web-development/scripting-and-backend/node-js/advanced/... 24 /web-development/scripting-and-backend/node-js/fundamentals/... 24 /web-development/scripting-and-backend/node-js/intermediate/... example: office-and-productivity-software/excel/fundamentals/excel_fundamentals_1_6.html -> /office-and-productivity-software/excel/fundamentals/solutions/excel1-6_challenge1.txt This is a second, independent way to the same number the Django project found in Chapter 11 of that course (178 files "not found"), and the cause is the same: 174 of them are solution links in web-development/scripting-and-backend courses whose files were moved or never made. The check belongs in the build before launch; it was NOT made to fail the build here, because 178 known problems would stop every build until someone decides what to do about them. PART C: images and fonts. Next.js has next/image (resizes and converts images) and next/font (loads fonts without a layout jump). Whether they are worth using depends on what the pages really contain, so the fragments were surveyed: Save as survey-assets.mjs: // What do the real fragments load from outside themselves? images, fonts, scripts, stylesheets, copy buttons. import { loadPages, prepareFragment, readContentFile } from "./packages/content/src/index.ts"; const root = process.env.LW_CONTENT_ROOT; const { pages } = loadPages(root); const count = {}; const bySite = {}; const bump = (k, site) => { count[k] = (count[k] || 0) + 1; (bySite[k] ??= {})[site] = (bySite[k][site] || 0) + 1; }; const examples = {}; for (const page of pages) { const f = prepareFragment(readContentFile(root, page.path), page.path, page.site); const imgs = f.match(/]*>/gi) ?? []; if (imgs.length) { bump("pages with ", page.site); (examples.img ??= []).push(...imgs.slice(0, 1)); } if (/@font-face/i.test(f)) bump("pages with @font-face", page.site); if (/fonts\.googleapis|fonts\.gstatic/i.test(f)) bump("pages loading Google Fonts", page.site); if (/]+rel=["']stylesheet/i.test(f)) bump("pages with ", page.site); if (/]+src=/i.test(f)) { bump("pages with