Open jpnws opened 1 year ago
NextJs 13 still don´t support some other libraries like this one because they have to update it to the app folder. If you want the pwa you have to do it with pages folder
@jpnws are you adding your pages into the app
directory?
Not a fix, but a behavior I found that gets PWA working briefly on apps not using /pages/
:
Comment out appDir on next.config.js
(breaks website)
npm run build
, npm run dev
Open localhost:3000
Uncomment appDir
npm run build
, npm run dev
Refresh webpage, and the PWA should work
console
and applications
tab in dev tools. Once I ran Lighthouse
, things stop working again./** @type {import('next').NextConfig} */
const withPWA = require('next-pwa')({
dest: 'public',
})
module.exports = withPWA({
experimental: {
appDir: true, // <---- Comment and Uncomment this
},
})
@jpnws are you adding your pages into the
app
directory?
Yes I'm using the new app directory on NextJS 13.
I ended up forking this repo: https://github.com/DuCanhGH/next-pwa (it works with App Router and has built-in TypeScript, JSDoc support - all examples are also written in TypeScript).
This happens because this line doesn't check for app-build-manifest.json
alongside with build-manifest.json
although it is supposed to as there hasn't been any activity on this repo since App Router was released.
One alternative is changing your next-pwa
config:
// next.config.js
const withPWAInit = require("next-pwa");
const isDev = process.env.NODE_ENV !== "production";
const withPWA = withPWAInit({
// your other config...
exclude: [
// add buildExcludes here
({ asset, compilation }) => {
if (
asset.name.startsWith("server/") ||
asset.name.match(/^((app-|^)build-manifest\.json|react-loadable-manifest\.json)$/)
) {
return true;
}
if (isDev && !asset.name.startsWith("static/runtime/")) {
return true;
}
return false;
}
],
});
/** @type {import("next").NextConfig} */
const nextConfig = {
// your other config...
webpack(config) {
const registerJs = path.join(path.dirname(require.resolve("next-pwa")), "register.js");
const entry = config.entry;
config.entry = () =>
entry().then((entries) => {
// Automatically registers the SW and enables certain `next-pwa` features in
// App Router (https://github.com/shadowwalker/next-pwa/pull/427)
if (entries["main-app"] && !entries["main-app"].includes(registerJs)) {
if (Array.isArray(entries["main-app"])) {
entries["main-app"].unshift(registerJs);
} else if (typeof entries["main-app"] === "string") {
entries["main-app"] = [registerJs, entries["main-app"]];
}
}
return entries;
});
return config;
},
}
module.exports = nextConfig;
Edit: updated the solution based on @Schular's comment.
Does anyone know if there are any plans to also add this fix to this repo anytime soon?
Any update, or any alternative?
Done! Create a head.tsx in app root directory as client component, then with a use effect hook register the service worker.
@AlainYRS you got an example you could paste in?
I can get it to register in the header but get the following error
Uncaught (in promise) bad-precaching-response: bad-precaching-response :: [{"url":"http://localhost:3000/_next/app-build-manifest.json","status":404}]
@AaronLayton I don't think his solution would work. If you want to use next-pwa, you can try adding this to your next-pwa config:
const withPWAInit = require("next-pwa");
const isDev = process.env.NODE_ENV !== "production";
const withPWA = withPWAInit({
// your other config
exclude: [
// add buildExcludes here
({ asset, compilation }) => {
if (
asset.name.startsWith("server/") ||
asset.name.match(/^((app-|^)build-manifest\.json|react-loadable-manifest\.json)$/)
) {
return true;
}
if (isDev && !asset.name.startsWith("static/runtime/")) {
return true;
}
return false;
}
],
});
// your Next config
and you still have to register the SW yourself.
Thanks @DuCanhGH that works for me. I did try and exclude the app-build-manifest
but think I was using buildExcludes
instead of exclude
.
Temporary fix
For anyone coming here for the same reason and before this PR gets merged - https://github.com/shadowwalker/next-pwa/pull/427 - the below works for me in production with the following caveat.
This excludes the nextjs build manifest which includes all the page URLs that get generated. In my testing, the below will pre cache static files but it won't cache any URL until you first visit the page after the service-worker has been installed on first visit.
next-pwa": "^5.6.0
next.config.js
const withPWAInit = require("next-pwa");
const isDev = process.env.NODE_ENV !== "production";
const withPWA = withPWAInit({
dest: 'public',
disable: isDev,
exclude: [
// add buildExcludes here
({ asset, compilation }) => {
if (
asset.name.startsWith("server/") ||
asset.name.match(/^((app-|^)build-manifest\.json|react-loadable-manifest\.json)$/)
) {
return true;
}
if (isDev && !asset.name.startsWith("static/runtime/")) {
return true;
}
return false;
}
],
});
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
},
}
module.exports = withPWA(nextConfig);
app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html
lang="en"
>
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body
className="min-h-[100svh]"
>
<Providers>
{children}
</Providers>
<Pwa />
</body>
</html>
)
}
Custom PWA Header component that runs in the client
app/Pwa.tsx
"use client"
import { useEffect } from "react";
export default function Pwa() {
let sw: ServiceWorkerContainer | undefined;
if (typeof window !== "undefined") {
sw = window?.navigator?.serviceWorker;
}
useEffect(() => {
if (sw) {
sw.register("/sw.js", { scope: "/" }).then((registration) => {
console.log("Service Worker registration successful with scope: ", registration.scope);
}).catch((err) => {
console.log("Service Worker registration failed: ", err);
});
}
}, [sw]);
return (
<></>
)
}
@AaronLayton I think <HeadPwa />
should be put in your app/layout.tsx
instead.
And that PR won't completely fix the problem, it will only make auto registration work in appDir, it won't fix that bad-precaching-response
issue. Not that I think the maintainers will merge it, I think this repo seems dead (despite the author saying otherwise).
Also, disable: process.env.NODE_ENV === 'development'
can be changed to disable: isDev
. Just saying :)
@DuCanhGH fully agree - yeah the HeadPwa only ended up being there due to iterations to get to this point. Adding it to the layout.tsx
will be just as good. I have updated the snippet above.
By all means this is not a fix
but it should get people by for the time being.
I did try to contact @shadowwalker on Twitter to see if they were getting notifications or if there is any way I can help to get the PR merged in, but no luck yet ☹️
@AaronLayton I'd also suggest changing your app/Pwa.tsx
a bit:
import type { Workbox } from "workbox-window";
declare global {
interface Window {
workbox: Workbox;
}
}
export default function Pwa() {
useEffect(() => {
if (
"serviceWorker" in navigator &&
window.workbox !== undefined
) {
const wb = window.workbox;
wb.register();
}
}, []);
return <></>;
}
I think this looks best :) I also think it's kinda hard to contact the author, let alone have him accept the PR as he hasn't been working on this project for months...
@DuCanhGH I see you've made lots of changes since you forked the repo. I'm inclined to go with your fork instead but unsure what kind of impact your changes have on the package.
@Murkrage so far I've done these changes to the package:
appDir
(app/_offline/page.tsx
now also automatically enables offline fallback for document).babel
to swc
(the original package used next/babel
to build fallback/custom worker). swc-loader
and @swc/core
in your devDependencies
should you use any of these features (no longer the case in 8.1.0)PluginOptions.workboxOptions
(this change is why the package is at its 7.x right now, and if you have // @ts-check
at the top of your next.config.js
you should also be informed if you pass invalid options, for example having both GenerateSW-specfic and InjectManifest-specific options).const withPWA = require("@ducanh2912/next-pwa").default()
, rather than const withPWA = require("next-pwa")()
.app/~offline/page.js
to automatically enable the fallback worker.These are the ones I can pull off the top of my head :)
In v8, I've also added these:
tsconfig.json
path aliases in custom workers. (8.0.0)subdomainPrefix
and workboxOptions.exclude
. (8.0.0)extendDefaultRuntimeCaching
, which allows you to extend the default runtime caching array, rather than overriding it. (8.5.0)aggressiveFrontEndNavCaching
- when combined with cacheOnFrontEndNav
, it will cache <link rel="stylesheet" href=""
and <script src="" />
on page navigation. (8.7.0)These are the changes in v9:
next/dist/build/swc
first before @swc/core
to save disk space. (9.0.0)Had the same issue. @DuCanhGH You package solves the issue. Thank you!
I was struggling to implement PWA in Next13 App directory. Thank you @DuCanhGH. Your solution worked like a charm. :)
For anyone that wants a working solution using only the next-pwa
(keep in mind that this repo seems like is not maintained anymore), I created a sample repository with a workaround: next.config.js
The above repo is deployed on Vercel (app & pages dirs concomitantly) with the following pages:
This example is a simple Next.js starter project that should get ~100% on all Lighthouse categories.
Basically we add buildExcludes: ["app-build-manifest.json"]
at next-pwa
config, as the others are already excluded on the next-pwa
implementation (https://github.com/shadowwalker/next-pwa/issues/424#issuecomment-1332258575) and we modify the webpack config with the custom main-app
entry (https://github.com/shadowwalker/next-pwa/pull/427)
Thanks @DuCanhGH, floatingdino
@Schular nice, that sounds like a great solution! I've updated my own comment to match this repo as well.
@Schular does this solution work with bashPath?
@Schular does this solution work with bashPath?
Yes, you should have no problems.
For example adding basePath: "/test"
then you would have to update the path to all public assets to have the /test
prefix (manifest.json, favicon.ico, images, etc.), other than that should work out-of-the-box.
The above solutions worked for my next.js project using the /src
with /app
. My next issue was configuring the start_url. My start url is not /
so the service worker would not get registered. After installing the app and running it a 404 error would appear. Lighthouse test showed that no service worker was registered on the start_url
. The solution I found that lets you add a start_url
that is something other than /
:
start_url:/
to work.start_url
I also put my manifest and icons into a separate worker directory in /public
(optional)
If you did change the location of the manifest/icons then also make sure to update the routes of the icons in the manifest and the meta import for manifest.json sw:"nameOfWorker.js"
(Optional )start_url
useEffect(() => {
if ("serviceWorker" in navigator) {
window.addEventListener("load", function () {
navigator.serviceWorker.register("/nameofSW.js").then(
function (registration) {
console.log("Service Worker registration successful with scope: ", registration.scope);
},
function (err) {
console.log("Service Worker registration failed: ", err);
}
);
});
}
}, []);
const [installPromptEvent, setInstallPromptEvent] = useState(null);
!!!The service worker is still at root of website so you don't need to prefix it like the manifest.json
!!!
Edit: Is it a bad idea to go about it this way? Edit1: make sure the scope in the manifest is also "/" or the service worker wont have access to your page
hey @Schular i followed your examples except for using a manifest.ts file instead of manifest.json but everything builds locally but as soon as it goes up on vercel i get an error
Failed to compile.
--
10:20:00.811 |
10:20:00.811 | Worker is not a constructor
10:20:00.812 |
10:20:00.812 |
10:20:00.812 | > Build failed because of webpack errors
has anyone seen this before
@AaronLayton I don't think his solution would work. If you want to use next-pwa, you can try adding this to your next-pwa config:
const withPWAInit = require("next-pwa"); const isDev = process.env.NODE_ENV !== "production"; const withPWA = withPWAInit({ // your other config exclude: [ // add buildExcludes here ({ asset, compilation }) => { if ( asset.name.startsWith("server/") || asset.name.match(/^((app-|^)build-manifest\.json|react-loadable-manifest\.json)$/) ) { return true; } if (isDev && !asset.name.startsWith("static/runtime/")) { return true; } return false; } ], }); // your Next config
and you still have to register the SW yourself.
Thanks, this works for me too
After adding the workaround provided by @Schular I get the error unhandledRejection: Error: Entry app/page depends on main-app, but this entry was not found
in the terminal.
using next@npm:14.1.0 [d369e] next-pwa@npm:5.6.0 [d369e]
Summary
I was running into "Does not register a service worker that controls page and start_url" after migrating to NextJS v13.
After some investigation, I noticed that I was running into a bad precaching runtime error due to
http://localhost:3000/_next/app-build-manifest.json
.Does this have something to do with the fact that Next13 uses
app
directory?Versions:
next-pwa
: 5.6.0next
: 13Steps to reproduce the behavior: Migrate to NextJS v13.
Expected Behaviors: Service worker should be succesfully registered.