In my Next app I'm using next-pwa for offline support as well as next-auth for authentication. next-pwa is configured like this:
const customCache = [
{
urlPattern: /^https:\/\/use\.(?:typekit)\.net\/.*/i,
handler: 'CacheFirst',
options: {
cacheName: 'typekit-fonts',
expiration: {
maxEntries: 4,
maxAgeSeconds: 7 * 24 * 60 * 60 // 7 days
}
}
},
{
urlPattern: /^https:\/\/p\.(?:typekit)\.net\/.*/i,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'typekit-fonts-stylesheets',
expiration: {
maxEntries: 4,
maxAgeSeconds: 7 * 24 * 60 * 60 // 7 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: /\/_next\/image\?url=.+$/i,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'next-image',
expiration: {
maxEntries: 64,
maxAgeSeconds: 24 * 60 * 60 // 24 hours
}
}
},
{
urlPattern: /\.(?:mp3|wav|ogg)$/i,
handler: 'CacheFirst',
options: {
rangeRequests: true,
cacheName: 'static-audio-assets',
expiration: {
maxEntries: 32,
maxAgeSeconds: 24 * 60 * 60 // 24 hours
}
}
},
{
urlPattern: /\.(?:mp4)$/i,
handler: 'CacheFirst',
options: {
rangeRequests: true,
cacheName: 'static-video-assets',
expiration: {
maxEntries: 32,
maxAgeSeconds: 24 * 60 * 60 // 24 hours
}
}
},
{
urlPattern: ({ url }) => {
if (url.origin.includes('admin')) return false;
if (/\.(?:js)$/i.test(url.origin)) return true;
return false;
},
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: ({ url }) => {
if (url.origin.includes('admin')) return false;
if (/\/_next\/data\/.+\/.+\.json$/i.test(url.origin)) return true;
return false;
},
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'next-data',
expiration: {
maxEntries: 32,
maxAgeSeconds: 24 * 60 * 60 // 24 hours
}
}
},
{
urlPattern: ({ url }) => {
if (url.origin.includes('admin')) return false;
if (/\.(?:json|xml|csv)$/i.test(url.origin)) return true;
return false;
},
handler: 'NetworkFirst',
options: {
cacheName: 'static-data-assets',
expiration: {
maxEntries: 32,
maxAgeSeconds: 24 * 60 * 60 // 24 hours
}
}
},
{
urlPattern: ({ url }) => {
const isSameOrigin = self.origin === url.origin;
if (!isSameOrigin) return false;
const { pathname } = url;
if (pathname.startsWith('/api/') || pathname.includes('admin')) return false;
return true;
},
handler: 'NetworkFirst',
options: {
cacheName: 'same-origin',
expiration: {
maxEntries: 32,
maxAgeSeconds: 24 * 60 * 60 // 24 hours
},
networkTimeoutSeconds: 10
}
},
{
urlPattern: ({ url }) => {
const isSameOrigin = self.origin === url.origin;
return !isSameOrigin;
},
handler: 'NetworkFirst',
options: {
cacheName: 'cross-origin',
expiration: {
maxEntries: 32,
maxAgeSeconds: 60 * 60 // 1 hour
},
networkTimeoutSeconds: 10
}
}
];
const withPWA = require('next-pwa')({
dest: 'public',
register: true,
skipWaiting: true,
runtimeCaching: customCache,
buildExcludes: [/chunks\/pages\/admin-.*$/],
disable: process.env.NODE_ENV === 'development'
});
const nextConfig = {
reactStrictMode: true,
pageExtensions: ['page.jsx', 'page.js', 'page.tsx', 'page.ts'],
async exportPathMap() {
return {
'/public-error': {
page: '/[...error]',
query: { error: 'An error has occurred' }
}
};
}
};
My root URL is defined like this:
import type { GetServerSideProps } from 'next';
import { getServerSession } from 'next-auth/next';
import MainView from '~/components/page-main';
import { authOption } from '~/pages/api/auth/[...nextauth].page';
function MainViewPage() {
return <MainView />;
}
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getServerSession(req, res, authOption);
if (!session) {
return {
redirect: {
destination: `/login?redirectTo=${req.url}`,
permanent: false
}
};
}
return { props: {} };
};
export default MainViewPage;
Everything is working well when online. However, when offline the behavior is unexpected. First, suppose the user was previously already signed into the app, and they navigate to the app while offline. In that case, they get the login page (this is actually what I want. The login page has a special option to navigate to specific offline-available resources). However, if the user was not logged in, and navigated to the app when offline, the screen comes up blank.
The only error I get in the console is this:
Uncaught SyntaxError: Invalid or unexpected token (at vendors.js:1:86604)
This is what happens when I run the app from an actual remote server (over HTTPS) and really turns off the internet connection on my machine. If I run the app in localhost and sets it to offline in the dev tools none of this happens, and everything is working as expected.