cf.completefrontend
Open lab My progress
THE NEXT.JS FIELD GUIDE

Static files and the public folder

Serve files from public in Next.js, import a file to get a hashed URL cached for a year, read each Cache-Control header, and add an app icon.

By the end, you can
  • 01
    Serve a file from publicGive a file in public its address, and say what Next.js sends with it and why browsers check it again every time.
  • 02
    Import a file insteadImport an image from your code, read its src, width and height, and tell why its new name lets browsers keep it for a year.
  • 03
    Pick the right placeChoose between public, an import and an icon file in app, and set a Cache-Control header of your own when a public file needs one.

The tea shop's logo, served from public

A static file is a file the server sends exactly as it is saved, the same for every visitor: a picture, a PDF, a text file. Next.js has two ways to serve one. You can put it in the public folder, or import it from your code. The two ways give the file different addresses, and browsers keep each one for a different time. Start with the shop's logo, a small picture of a teacup, saved as public/logo.svg. (SVG is a picture format written as text, so you can read the file in the example.)

The home page shows the logo from publicNext.js
app/public/
app/page.tsx
export default function Home() {  return (    <main>      <img src="/logo.svg" alt="Tea shop logo" width={120} height={80} />      <h1>Tea shop</h1>      <p>Green tea, mint tea and lemon tea.</p>    </main>  );}
Recorded from Next.js 16.4.3
localhost:3000/Tea shop
The page at /, as Chrome drew it with its styles. It says: Tea shop. Green tea, mint tea and lemon tea.

A picture of the page in real Chrome, in a window 800 px wide, taken once the page had loaded with its styles. Open it full size

This is a real Next.js 16.4.3 project, built with next build and run with next start. Line 4 of app/page.tsx is a plain <img> whose src is /logo.svg: the file's path inside public, starting at the top of the site. The Browser tab shows a picture of the page in real Chrome, with the green teacup above the heading.

Now pick /logo.svg above the page, then open the Network tab. The server sent the file's text, byte for byte, with two headers worth reading:

  • Content-Type: image/svg+xml tells the browser what kind of file it is. Next.js picks it from the file's ending, .svg.
  • Cache-Control: public, max-age=0 tells browsers, and other caches on the way, how long they may keep a copy. A cache is a place that stores a copy of a response, so it doesn't have to be downloaded again. Here, a copy may be stored, but it's out of date the moment it arrives.
A browser window for the page slash shows the logo and the heading Tea shop. An arrow labeled GET slash logo dot svg goes to a server window holding public slash logo dot svg. An arrow comes back, labeled the same bytes, with the header Cache-Control: public, max-age=0./logoTea shopGET /logo.svgnext startpublic/logo.svgthe same bytesCache-Control: public, max-age=0
The page's HTML only holds the address /logo.svg. The browser asks for it, and Next.js sends public/logo.svg byte for byte, with Cache-Control: public, max-age=0.

Project structure gave public a first look, with a text file and the same header. A picture you import instead gets a different address and a different header.

Real-life analogyA price list on the door, and a printed catalog

A tea shop pins its price list on the door. The owner can replace the sheet whenever prices change, and the door stays the same, so a regular customer reads it again on every visit. The shop also prints a catalog with an edition number on the cover. Edition 7 never changes. When prices change, the shop prints edition 8. A customer who owns edition 7 never has to ask whether it's still edition 7.

In real life: The price list pinned on the shop's door
In Next.js: A file in public, at /logo.svg
In real life: Anyone can walk up and read it
In Next.js: Anyone can download it
In real life: The owner may swap the sheet any day, so regulars read it again on every visit
In Next.js: max-age=0: check with the server before using a stored copy
In real life: A catalog with an edition number on the cover
In Next.js: An imported file, with a name the build makes
In real life: Edition 7 never changes; new prices mean edition 8
In Next.js: immutable: new content gets a new name
The main idea

A file in public keeps its own address forever, so browsers must check it again before every use. A file you import gets a new name from the build, which changes whenever the file changes, so browsers may keep it for a year. Use public when the address itself matters. Import a file when only your own code uses it.

What the public folder sends

Every file in public gets an address: its path inside the folder, with / in front. Folders inside public become parts of the address, so public/teas/mint.svg is /teas/mint.svg. The word public is never part of it. Here the page asks for /public/logo.svg:

Asking for the logo with public in its addressNext.js
app/public/
app/page.tsx
export default function Home() {  return (    <main>      <img src="/public/logo.svg" alt="Tea shop logo" width={120} height={80} />      <h1>Tea shop</h1>      <p>Green tea, mint tea and lemon tea.</p>    </main>  );}
Recorded from Next.js 16.4.3
localhost:3000/Tea shop
The page at /, as Chrome drew it with its styles. It says: Tea shop. Green tea, mint tea and lemon tea.

A picture of the page in real Chrome, in a window 800 px wide, taken once the page had loaded with its styles. Open it full size

In the Browser tab, real Chrome shows the logo's alt text where the picture should be. Pick /public/logo.svg and open the Network tab: the server answered 404, with Next.js's page for addresses that don't exist. There's no public/public/logo.svg, and no page at that address.

Four more things are true of every file in public:

  • It isn't built. next build doesn't read, change or rename it. In the recording, the logo's text is exactly the file's text.
  • It's public. Anyone who knows the address can download it, linked or not. Secrets never go there.
  • A missing file shows up late. A typo in "/logo.svg" builds without a warning, and only a visitor's browser finds the 404.
  • Its list is made when the server starts, in the recorded version. In Next.js 16.4.3, the production server that next start runs reads the names of the files in public once, when it starts, and serves only those, as its source shows. A file copied in later answers 404 there until the server starts again. That's how this version works, not a promise in the docs, and a host can serve the folder its own way. Either way, pictures that people upload while the shop runs belong in a storage service made for that, not in public.

One more line in the recorded HTML is worth a look. In the Network tab of the first example, this recording's <head> has <link rel="preload" href="/logo.svg" as="image">. React's server renderer added it, because the <img> isn't marked loading="lazy": it tells the browser to start downloading the logo early, before it reaches the <img> in the body.

Import a file, and the build names it

The second way: keep a picture next to the code that uses it, and import it like a module (import and export shows how imports work). The shop has a leaf picture in app/leaf.svg, and the home page imports it:

The home page imports the leafNext.js
app/public/
app/page.tsx
import leaf from "./leaf.svg"; export default function Home() {  return (    <main>      <img src="/logo.svg" alt="Tea shop logo" width={120} height={80} />      <h1>Tea shop</h1>      <img src={leaf.src} alt="A tea leaf" width={leaf.width} height={leaf.height} />      <p>The leaf is at {leaf.src}</p>    </main>  );}
Recorded from Next.js 16.4.3
localhost:3000/Tea shop
The page at /, as Chrome drew it with its styles. It says: Tea shop. The leaf is at /_next/static/media/leaf.2z1sukzkjtw3f.svg.

A picture of the page in real Chrome, in a window 800 px wide, taken once the page had loaded with its styles. Open it full size

Line 1 imports the file. What it gives back isn't the picture: it's a small object that Next.js makes during the build, with three values you use:

  • leaf.src, the address the build gave the file. Line 8 puts it in the <img>, and line 9 prints it.
  • leaf.width and leaf.height, read from the file itself. Here they're 80 and 80, from the width and height in line 1 of app/leaf.svg. A picture with its size in the HTML gets its space before it loads, so the text under it doesn't jump.

The Browser tab shows the page in real Chrome. The leaf's address, printed under it, is /_next/static/media/leaf.2z1sukzkjtw3f.svg. That address comes from the build, not from you: a generated address with a hash in it, a short code the build makes, which changes when the file changes. The folder, the name and the hash's format are this recording's, chosen by the bundler, and can differ in another version or setup, so code always uses leaf.src. Pick the third response in the Network tab, the leaf itself: Cache-Control: public, max-age=31536000, immutable.

Try a redesign. Before you click, guess: which of the two addresses changes when both pictures turn orange?

Playground: redesign the shop in orange
The tea shop, with this version of its two picturesNext.js
app/public/
app/leaf.svg
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 80 80">  <path d="M10 70 C10 30 40 10 70 10 C70 40 50 70 10 70 Z" fill="#2f855a" /></svg>
Recorded from Next.js 16.4.3
localhost:3000/Tea shop
The page at /, as Chrome drew it with its styles. It says: Tea shop. The leaf is at /_next/static/media/leaf.2z1sukzkjtw3f.svg.

A picture of the page in real Chrome, in a window 800 px wide, taken once the page had loaded with its styles. Open it full size

Try it yourself
Green

The shop as it was. The page prints the leaf's address: a hashed address the build made. The logo is at /logo.svg.

Both versions were built with next build from Next.js 16.4.3 and opened in real Chrome. Only the colors in app/leaf.svg and public/logo.svg change. Reset goes back to the green shop.

In this recording, the leaf moved to /_next/static/media/leaf.0nag7zihy53fz.svg. New content, a new hashed address, and the new HTML points at it. The logo is still at /logo.svg, though its content changed too.

Four rows, each a file and the address the browser gets for it. Imported from app: the green leaf.svg becomes …/media/leaf.2z1sukzkjtw3f.svg, and the orange leaf.svg becomes …/media/leaf.0nag7zihy53fz.svg, a different name. From public: the green logo.svg and the orange logo.svg both stay at slash logo dot svg.Imported from appIn publicleaf.svg, green…/media/leaf.2z1sukzkjtw3f.svgleaf.svg, orange…/media/leaf.0nag7zihy53fz.svglogo.svg, green/logo.svglogo.svg, orange/logo.svgnext build
From the recordings: imported, the green leaf and the orange leaf get two different names, so an address always means the same picture. In public, both logos have the same address, /logo.svg, so a stored copy may be the old one.

Importing has one more thing going for it. A wrong path stops the build instead of showing a 404 to visitors. With app/leaf.svg deleted, the build stops at line 1 of the page:

The leaf is imported, but the file isn't thereNext.js
app/public/
app/page.tsx
import leaf from "./leaf.svg"; export default function Home() {  return (    <main>      <img src="/logo.svg" alt="Tea shop logo" width={120} height={80} />      <h1>Tea shop</h1>      <img src={leaf.src} alt="A tea leaf" width={leaf.width} height={leaf.height} />      <p>The leaf is at {leaf.src}</p>    </main>  );}
next build failed · Next.js 16.4.3
$ next build▲ Next.js 16.4.3 (Turbopack)- Cache Components enabled- Partial Prefetching enabled   Creating an optimized production build ... > Build error occurredError: Turbopack build failed with 1 error:./app/page.tsx:1:1Error: Module not found: Can't resolve './leaf.svg'> 1 | import leaf from "./leaf.svg";    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  2 |  3 | export default function Home() {  4 |   return ( https://nextjs.org/docs/messages/module-not-found     at <unknown> (https://nextjs.org/docs/messages/module-not-found)

Each row compares the two ways on one question:

public or an import
QuestionA file in publicAn imported file
Where the file livesIn public, like public/logo.svg.Anywhere in your code, like app/leaf.svg.
How the page points at itBy its address, written by hand: "/logo.svg".With import leaf from "./leaf.svg", then leaf.src.
Its addressThe same as its path in public, forever.A generated address with a hash in it, made by the build. It changes when the file changes.
How long browsers keep itThey check it again before every use.For a year, without asking.
A missing fileFound only when someone asks for it: a 404.The build fails, because the import can't be found.

Imported pictures also work with next/image: <Image src={leaf} alt="A tea leaf" /> reads the width and height from the object, so you don't write them. The Optimizing images lesson in this stage teaches it.

How long browsers keep each file

Cache-Control is a response header made of instructions for caches. The recorded files use three:

  • public: any cache may store the response, the browser's own and the shared ones on the way, like a CDN (a network of servers close to visitors that keep copies).
  • max-age=N: the stored copy counts as up to date for N seconds. With 0, it's out of date at once, so the browser asks the server whether the file changed before using it. When it hasn't, the answer is short, but it's still a request.
  • immutable: the content at this address never changes, so while the copy is up to date, the browser has no reason to ask.

Next.js can't know whether you'll replace public/logo.svg tomorrow under the same name, so it gives public files max-age=0. An imported file's hashed address changes when the file does, so the recorded leaf came with a year and immutable. In this version's source, the build's own files (here the leaf, and the page's JavaScript and CSS, all under /_next/static/) get that header with two exceptions: a file that already has a Cache-Control header keeps it, and service worker files under /_next/static/service-worker/ get public, max-age=0, must-revalidate. Pick a file below and read its header in the Network tab:

Playground: which Cache-Control header does each file get?
The tea shop, asked for one of its picturesNext.js
app/public/
app/page.tsx
export default function Home() {  return (    <main>      <img src="/logo.svg" alt="Tea shop logo" width={120} height={80} />      <h1>Tea shop</h1>      <p>Green tea, mint tea and lemon tea.</p>    </main>  );}
Recorded from Next.js 16.4.3

GET /logo.svg200

Response headers
Cache-Control public, max-age=0
Content-Type image/svg+xml
Vary Accept-Encoding

The body that arrived, as it arrived

<svg xmlns="http://www.w3.org/2000/svg" width="120" height="80" viewBox="0 0 120 80">
  <rect x="20" y="16" width="60" height="48" rx="12" fill="#2f855a" />
  <circle cx="88" cy="40" r="12" fill="none" stroke="#2f855a" stroke-width="6" />
</svg>
Try it yourself
A file in public

Line 4 points at /logo.svg, a file in public. It came with Cache-Control: public, max-age=0: browsers may store it, but must check with the server before using it again.

Every version was built with next build from Next.js 16.4.3, and the Network tab shows what next start sent for the picture. Reset goes back to the first version.

The second version sets a header of its own with headers() in next.config.ts. Lines 9 to 12 say: for the address /logo.svg, send Cache-Control: public, max-age=86400. The recording shows that header replaced Next.js's, so browsers may now use their copy for a day without asking. That's faster on the next visit, with a cost: change the logo, and some visitors see the old one for up to a day. When a public file needs a long cache, give each version a new file name, like logo-2.svg.

next.config.ts: a header of your own for /logo.svgTypeScript
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  cacheComponents: true,
  partialPrefetching: true,
  async headers() {
    return [
      {
        source: "/logo.svg",
        headers: [{ key: "Cache-Control", value: "public, max-age=86400" }],
      },
    ];
  },
};

export default nextConfig;

headers() is an async function that returns a list of rules. Each rule has a source, the address it matches, and the headers to add, each a key and a value. Lines 5 and 6 are create-next-app's settings, kept as they were.

Each row is one file from the recordings, with the header it came with and what that header allows:

Who may keep each file, and for how long
FileCache-ControlWhat it allows
/logo.svg, from publicpublic, max-age=0Any cache may store it, but it's out of date at once. Before using it again, the browser asks the server whether it changed.
/logo.svg, with a header in next.config.tspublic, max-age=86400Any cache may use it for a day (86,400 seconds) without asking. A changed logo can show up to a day late.
The imported leaf, at its hashed addresspublic, max-age=31536000, immutableAny cache may use it for a year without asking. A changed leaf gets a new hashed address, so the old copy is never wrong.
/icon.svg, from app/icon.svgpublic, max-age=0, must-revalidateChecked again before every use, like a file in public. The tag in the page adds a hash to its address.
A second visit to the shop. A window for the browser's stored copies holds slash logo dot svg, marked max-age=0, and …/media/leaf.2z1sukzkjtw3f.svg, marked immutable. An arrow from the logo goes to the server, labeled changed?. The leaf has no arrow: it's used as it is, with no request.stored copies/logo.svgmax-age=0: check first…/media/leaf.2z1sukzkjtw3f.svgimmutable: use it as it ischanged?serverno requestfor the leaf
On the next visit, the browser asks the server whether /logo.svg changed before it shows its copy. It shows the leaf from its copy without asking: for a year, nothing can change at that address.

Favicons and app icons: a first look

The small picture in a browser tab, a bookmark or a search result is the site's icon, often called a favicon. In the App Router, the docs say to make it a metadata file: a file with a special name at the top of app: favicon.ico, which create-next-app puts there, or icon with an image ending, like icon.svg or icon.png. Here is the shop with app/icon.svg:

An app icon in app/icon.svgNext.js
app/public/
app/icon.svg
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">  <rect width="32" height="32" rx="8" fill="#2f855a" />  <path d="M8 24 C8 14 15 8 24 8 C24 17 18 24 8 24 Z" fill="#ffffff" /></svg>
Recorded from Next.js 16.4.3
$ next build▲ Next.js 16.4.3 (Turbopack)- Cache Components enabled- Partial Prefetching enabled   Creating an optimized production build ...✓ Compiled successfully  Running TypeScript ...  Finished TypeScript ...  Collecting page data ...✓ Generating static pages (4/4)  Finalizing page optimization ... Route (app)┌ ○ /├ ○ /_not-found└ ○ /icon.svg ○  (Static)  prerendered as static content

Three things happened, and you wrote no code for them. The Terminal tab lists ○ /icon.svg: Next.js made a route that serves the file. In the Network tab, the home page's <head> has this tag, which tells the browser where the icon is:

The tag Next.js added to the recorded page's headHTML
<link rel="icon" href="/icon.svg?icon.3oojcfa4q0a79.svg" sizes="any" type="image/svg+xml">

sizes="any" says an SVG can be drawn at any size, and type gives its kind. After the ? in href comes a hash. The docs say Next.js adds it so the icon can be cached well. And /icon.svg itself came with Cache-Control: public, max-age=0, must-revalidate: checked before every use, like a file in public.

Older projects put favicon.ico in public. Like any file there, it's served at its own address, /favicon.ico, and browsers ask for that address on their own. Next.js's docs still say to prefer the metadata files in app, which come with the tag. The Metadata API lesson shows the rest of the <head>, including icons written by hand in a layout's metadata. Icon files win over them. Icons made with code, Apple's home-screen icon and link-preview pictures are in the Metadata and SEO lessons of this stage.

app/icon.svgHTML
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
  <rect width="32" height="32" rx="8" fill="#2f855a" />
  <path d="M8 24 C8 14 15 8 24 8 C24 17 18 24 8 24 Z" fill="#ffffff" />
</svg>

Where does each file go?

Ask one question about each file: does its address have to stay the same? If people or tools outside your code use the address, put the file in public. If only your own components use it, import it. If it's the site's icon or a file search engines read, use the special file in app.

public, an import, or a special file?
  • The logo in the shop's header
  • A price list PDF that last month's emails link to
  • A file a search engine asks you to put at /google1234.html to prove you own the site
  • The small picture in the browser tab
  • A photo of mint tea inside a product card component
  • The robots.txt file that search engines read
Try it yourself
0 of 6 correct

Sort each file of the tea shop into the place it belongs.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "Next.js caches files in public for a long time." No. They come with public, max-age=0, so browsers check them again before every use. Only a header of your own changes that.
  • "An imported picture becomes part of the JavaScript." No. It stays a file of its own under address the build made, and the HTML points at it. The import only gives your code its address and size.
  • "immutable means the picture can never change." It means the content at that address never changes. Change the file, and the next build gives it a new address.
  • "The hash in a generated address is part of the API." No. Its format is the bundler's choice. Always use leaf.src, never a name copied from a build.
  • "You can upload files into public while the app runs." Not reliably. The recorded version's next start lists the folder when it starts, so a file added later answers 404 there. Uploads need storage made for them.
  • "A file in public is private unless a page links to it." No. Anyone who guesses or learns the address can download it.

Practice exercises

These exercises are about Next.js projects, which can't run inside this page. Answer first, then use the hints.

Exercise 1 · Warm-upRead the logo's header

Line 4 shows public/logo.svg. Which Cache-Control header does the server send with /logo.svg?

Starter codeTSX
export default function Home() {
  return (
    <main>
      <img src="/logo.svg" alt="Tea shop logo" width={120} height={80} />
      <h1>Tea shop</h1>
      <p>Green tea, mint tea and lemon tea.</p>
    </main>
  );
}

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upFind the broken address

    The logo doesn't show. Which status does the server answer for /public/logo.svg, and how do you fix line 4?

    Starter codeTSX
    export default function Home() {
      return (
        <main>
          <img src="/public/logo.svg" alt="Tea shop logo" width={120} height={80} />
          <h1>Tea shop</h1>
          <p>Green tea, mint tea and lemon tea.</p>
        </main>
      );
    }
    

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeShow the leaf on the About page

      Write app/about/page.tsx so it shows the leaf from app/leaf.svg. Does its address match the home page's leaf?

      Starter codeTSX
      // app/about/page.tsx
      export default function AboutPage() {
        // Import the leaf from app/leaf.svg, show it, and print its address
      }

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeWhich address changes?

        Both pictures change from green to orange, and the shop is built again. Which address changes: /logo.svg, or the leaf's?

        Starter codeTSX
        import leaf from "./leaf.svg";
        
        export default function Home() {
          return (
            <main>
              <img src="/logo.svg" alt="Tea shop logo" width={120} height={80} />
              <h1>Tea shop</h1>
              <img src={leaf.src} alt="A tea leaf" width={leaf.width} height={leaf.height} />
              <p>The leaf is at {leaf.src}</p>
            </main>
          );
        }
        

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeLet browsers keep the logo for a day

          Add a rule to next.config.ts so browsers may keep /logo.svg for a day without asking. Which header does it come with then?

          Starter codeTypeScript
          import type { NextConfig } from "next";
          
          const nextConfig: NextConfig = {
            /* config options here */
            cacheComponents: true,
            partialPrefetching: true,
            // Add a Cache-Control header for /logo.svg here
          };
          
          export default nextConfig;
          

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeGive the shop an icon

            You save this SVG as app/icon.svg. Which kind of tag does Next.js add to every page's <head>?

            Starter codeHTML
            <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
              <rect width="32" height="32" rx="8" fill="#2f855a" />
              <path d="M8 24 C8 14 15 8 24 8 C24 17 18 24 8 24 Z" fill="#ffffff" />
            </svg>
            

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask where the file lives, and whether its address can change.

              The static files quiz · 7 questionsScore: first tries count
              1. Question 1 of 7A file is saved at public/teas/mint.svg. Which address does a page use for it?

                Choose an answer to see the explanation.

              2. Question 2 of 7Why does Next.js send files in public with Cache-Control: public, max-age=0?

                Choose an answer to see the explanation.

              3. Question 3 of 7This is the page. What does leaf.src hold?

                Read the code, then predictTSX
                import leaf from "./leaf.svg";
                
                export default function Home() {
                  return (
                    <main>
                      <img src="/logo.svg" alt="Tea shop logo" width={120} height={80} />
                      <h1>Tea shop</h1>
                      <img src={leaf.src} alt="A tea leaf" width={leaf.width} height={leaf.height} />
                      <p>The leaf is at {leaf.src}</p>
                    </main>
                  );
                }
                

                Choose an answer to see the explanation.

              4. Question 4 of 7Why may browsers keep an imported file for a year, with immutable?

                Choose an answer to see the explanation.

              5. Question 5 of 7With this next.config.ts, which Cache-Control header does /logo.svg get?

                Read the code, then predictTypeScript
                import type { NextConfig } from "next";
                
                const nextConfig: NextConfig = {
                  /* config options here */
                  cacheComponents: true,
                  partialPrefetching: true,
                  async headers() {
                    return [
                      {
                        source: "/logo.svg",
                        headers: [{ key: "Cache-Control", value: "public, max-age=86400" }],
                      },
                    ];
                  },
                };
                
                export default nextConfig;
                

                Choose an answer to see the explanation.

              6. Question 6 of 7You add app/icon.svg. What does Next.js do with it?

                Choose an answer to see the explanation.

              7. Question 7 of 7The shop runs with next start from Next.js 16.4.3, the recorded version. You copy a new file into public. What happens when someone asks for it?

                Choose an answer to see the explanation.

              Key takeaways

              • A file in public is served at its path inside the folder, as it is, to anyone. public itself isn't part of the address.
              • Next.js sends public files with Cache-Control: public, max-age=0: browsers check them again before every use, because the file may change under the same name.
              • An imported file gets a hashed address from the build, with src, width and height. The address changes when the file changes, so the recorded leaf came with max-age=31536000, immutable.
              • The name's exact format belongs to the build. Use leaf.src; never copy a name from a build.
              • headers() in next.config.ts can give a public file a longer cache. Then rename the file when it changes.
              • The site's icon is app/favicon.ico or app/icon.svg: Next.js serves it and adds the <link rel="icon"> tag.

              Remember the one-liner.
              public keeps the address and checks every time; an import changes the name and keeps it for a year.

              Read it in Next.js's own code

              These links point at release v16.4.3, the version every example on this page was built with. The year-long header for files under /_next/static/ is set in lines 704 to 722 of router-server.ts, only when nothing else set a Cache-Control header first, and not for service worker files. The list of public files that next start makes when it starts is at lines 298 to 313 of filesystem.ts.

              Coming next: The Metadata API. It writes the rest of each page's <head>: titles, descriptions and link previews, from a metadata object in a layout or a page.

              CompleteFrontend Clear concepts. Working examples.