next-pwa icon indicating copy to clipboard operation
next-pwa copied to clipboard

next/image support

Open cullylarson opened this issue 4 years ago • 8 comments
trafficstars

Next.js recently introduced next/image. It doesn't look like next-pwa supports it.

Without using next/image, next-pwa will pre-cache all the images in /public. Then when offline, the images load as expected. However, if the images are served using next/image, the static images are still pre-cached, but they won't load when offline. This is because the image URLs are modified by next/image to look like http://localhost:3006/_next/image?url=....

It would be nice if next-pwa somehow recognized those URLs and was able to serve the pre-cached image, if available.

Let me know if I can clarify anything.

If this feature request would better be posted in the vercel/next.js repo, let me know.

cullylarson avatar Jan 25 '21 22:01 cullylarson

See #150 on how you might be able to cache them, still testing it, but very difficult due some limitations for which I made a feature request in #150.

marcofranssen avatar Jan 28 '21 19:01 marcofranssen

@marcofranssen That's a great alternative. Though, ideally: since it's easy to precache images (just put them in /public), if next-pwa could recognize when an image is being requested that is already precached from /public, it would just serve that image.

cullylarson avatar Jan 28 '21 20:01 cullylarson

I also ran into this issue. @marcofranssen -- did you manage to start caching those images?

ortonomy avatar Mar 04 '21 09:03 ortonomy

@ortonomy yes I did get it working as far I was able to debug. See here my pwa config in next.config.js.

pwa: {
    disable: process.env.NODE_ENV === "development",
    dest: "public",
    publicExcludes: ["!downloads/**/*", "!images/**/*"],
    runtimeCaching: [
      {
        // MUST be the same as "start_url" in manifest.json
        urlPattern: "/",
        // use NetworkFirst or NetworkOnly if you redirect un-authenticated user to login page
        // use StaleWhileRevalidate if you want to prompt user to reload when new version available
        handler: "NetworkFirst",
        options: {
          // don't change cache name
          cacheName: "start-url",
          expiration: {
            maxEntries: 1,
            maxAgeSeconds: 24 * 60 * 60, // 24 hours
          },
        },
      },
      {
        urlPattern: /^https:\/\/fonts\.(?:googleapis|gstatic)\.com\/.*/i,
        handler: "CacheFirst",
        options: {
          cacheName: "google-fonts",
          expiration: {
            maxEntries: 4,
            maxAgeSeconds: 365 * 24 * 60 * 60, // 365 days
          },
        },
      },
      {
        urlPattern: /\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,
        handler: "StaleWhileRevalidate",
        options: {
          cacheName: "static-font-assets",
          expiration: {
            maxEntries: 4,
            maxAgeSeconds: 7 * 24 * 60 * 60, // 7 days
          },
        },
      },
      {
        urlPattern: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,
        handler: "StaleWhileRevalidate",
        options: {
          cacheName: "static-image-assets",
          expiration: {
            maxEntries: 64,
            maxAgeSeconds: 24 * 60 * 60, // 24 hours
          },
        },
      },
      {
        urlPattern: /\.(?:js)$/i,
        handler: "StaleWhileRevalidate",
        options: {
          cacheName: "static-js-assets",
          expiration: {
            maxEntries: 32,
            maxAgeSeconds: 24 * 60 * 60, // 24 hours
          },
        },
      },
      {
        urlPattern: /\.(?:css|less)$/i,
        handler: "StaleWhileRevalidate",
        options: {
          cacheName: "static-style-assets",
          expiration: {
            maxEntries: 32,
            maxAgeSeconds: 24 * 60 * 60, // 24 hours
          },
        },
      },
      {
        urlPattern: /\.(?:json|xml|csv)$/i,
        handler: "StaleWhileRevalidate",
        options: {
          cacheName: "static-data-assets",
          expiration: {
            maxEntries: 32,
            maxAgeSeconds: 24 * 60 * 60, // 24 hours
          },
        },
      },
      {
        urlPattern: /\/api\/.*$/i,
        handler: "NetworkFirst",
        method: "GET",
        options: {
          cacheName: "apis",
          expiration: {
            maxEntries: 16,
            maxAgeSeconds: 24 * 60 * 60, // 24 hours
          },
          networkTimeoutSeconds: 10, // fall back to cache if api does not response within 10 seconds
        },
      },
      {
        urlPattern: /\/_next\/image\?url=.*$/i,
        handler: "StaleWhileRevalidate",
        options: {
          cacheName: "next-image",
          expiration: {
            maxEntries: 64,
            maxAgeSeconds: 24 * 60 * 60, // 24 hours
          },
        },
      },
      {
        urlPattern: /^https:\/\/raw\.githubusercontent\.com\/marcofranssen\/.*/i,
        handler: "StaleWhileRevalidate",
        options: {
          cacheName: "github-code-samples",
          expiration: {
            maxEntries: 64,
            maxAgeSeconds: 24 * 60 * 60, // 24 hours
          },
        },
      },
      {
        urlPattern: /^https:\/\/www\.google-analytics\.com\/.*/i,
        handler: "NetworkOnly",
      },
      {
        urlPattern: /^https:\/\/pagead2\.googlesyndication\.com\/.*/i,
        handler: "NetworkOnly",
      },
      {
        urlPattern: /^https:\/\/google.com\/ads\/.*/i,
        handler: "NetworkOnly",
      },
      {
        urlPattern: /^https:\/\/googleads\.g\.doubleclick\.net\/pagead\/ads.*/i,
        handler: "NetworkOnly",
      },
      {
        urlPattern: /^https:\/\/partner\.googleadservice\.com\/.*/i,
        handler: "NetworkOnly",
      },
      {
        urlPattern: /^https:\/\/adservice\.google\..*/i,
        handler: "NetworkOnly",
      },
      {
        urlPattern: /.*/i,
        handler: "NetworkFirst",
        options: {
          cacheName: "others",
          expiration: {
            maxEntries: 32,
            maxAgeSeconds: 24 * 60 * 60, // 24 hours
          },
          networkTimeoutSeconds: 10,
        },
      },
    ],
  },

The way I tested is by opening my page. Load some images. Then switch to offline mode in the developer tools and move arround between blogs I have recently openend. For those the images where still working. Allthough it isn't preloading them in this setup, that might be something someone is still able to improve in my setup as shown above. Open to feedback from the community here as well.

Also note I whitelisted following for images.

  images: {
    domains: ["og-image.now.sh"],
  },

Looking forward to feedback or improvements.

See here my current version of the blog https://nextjs.marcofranssen.nl

marcofranssen avatar Mar 25 '21 12:03 marcofranssen

Added the next-image default runtime cache rule in version 5.2.12

shadowwalker avatar Apr 18 '21 05:04 shadowwalker

@shadowwalker -- very nice!

ortonomy avatar Apr 18 '21 09:04 ortonomy

Damn, I wasted hours trying to make the images(which use next/image) to load...

iojcde avatar Apr 22 '21 05:04 iojcde

Added the next-image default runtime cache rule in version 5.2.12

This still breaks for me (using 5.2.21). Can you give an example of how the config should look like? Here's mine.

module.exports = withPWA({
  future: { webpack5: true },
  pwa: {
    dest: 'public',
    disable: process.env.NODE_ENV === 'development',
  },
  env: {
    siteTitle: 'site title',
  },
  images: {
    domains: ['source.unsplash.com'],
  },  
})

btahir avatar May 14 '21 04:05 btahir