Next.js PWA (Service Worker + Manifest.json) - manifest

I am using Next.js to develop a Server Side Rendering website and I want to make it a Progressive Web App but the problem I couldn't find the way to make it happen correctly.
When I build the application it serves correctly the service worker but there is no manifest.json and in some projects examples it serves the manifest.json but I tried it in Lighthouse audit and it says
Service worker does not successfully serve the manifest's start_url
One of the examples I used
Create Next App With Service Worker Precache
I think that the problem is that the start_url is . or / and not a valid file because in Next.js there is no index.html to serve from the start.
In summary
I am looking for an example using Next.js to build it to a dist folder and when I serve it it has a valid Service Worker and a valid Web Manifest.

A. Some file are expected to be found at "/"
You have this error because browsers expect some files to be served from the root of the server, including:
/manifest.json
/sitemap.xml
/favicon.ico
/robots.txt
/browserconfig.xml
/site.webmanifest
While the majority of these paths can be set with meta tags, older browsers just ignore them and error if these exact file names are not served.
B. Configure alternative paths and use NextJS static file
At the time of writing, there is ongoing work for supporting offline in NextJS. But it's not ready yet.
If you don't need to support older browsers and you don't want advanced SEO, you can use NextJS's Head component (see documentation) to define the manifest.json path like you would for any NextJS static file:
import Head from "next/head"
export default () => (
<Head>
<link rel="manifest" href="/static/manifest.json" />
<link rel="manifest" href="/static/site.webmanifest" />
<link rel="shortcut icon" href="/static/favicon.ico"
</Head>
)
Please note that robots.txt cannot be serve from a subdirectory (source), so this solution is not a good fit if you need to define this file.
C. Serve these files like expected
The proper solution would be to serve these files from your express server like so
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const { join } = require('path')
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const rootStaticFiles = [
'/manifest.json',
'/sitemap.xml',
'/favicon.ico',
'/robots.txt',
'/browserconfig.xml',
'/site.webmanifest',
]
if (rootStaticFiles.indexOf(parsedUrl.pathname) > -1) {
const path = join(__dirname, 'static', parsedUrl.pathname)
app.serveStatic(req, res, path)
} else {
handle(req, res, parsedUrl)
}
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
Note: This code directly comes from the NextJS examples repository

here are the steps to make your next.js progressive. check the example
npm i next-pwa
next.config.json
const withPWA = require("next-pwa");
module.exports = withPWA({
pwa: {
dest: "public",
},
...
});
add manifest.json and icons to public folder from the example. However, icons directory is missing "maskable_icon.png". So create a maskable icon from here then add this to "manifest.json".
{
"src": "path/to/maskable_icon.png",
"sizes": "196x196",
"type": "image/png",
"purpose": "any maskable"
}
add those tags to import Head from "next/head". Head is used for better SEO setting. check the documentation*
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"
/>
<meta name="description" content="Description" />
<meta name="keywords" content="Keywords" />
<title>Next.js PWA Example</title>
<link rel="manifest" href="/manifest.json" />
<link
href="/icons/favicon-16x16.png"
rel="icon"
type="image/png"
sizes="16x16"
/>
<link
href="/icons/favicon-32x32.png"
rel="icon"
type="image/png"
sizes="32x32"
/>
<link rel="apple-touch-icon" href="/apple-icon.png"></link>
<meta name="theme-color" content="#317EFB" />
</Head>
lastly check if it is working. add Lighhouse extension to chrome dev tools from chrome app store and run start the performance.

Related

ASP.NET Core 6 Web app with index.html using <base href> would not load script files

I have an ASP.NET Core 6 app and I host my Angular app with it, among other things (from Program.cs):
...
app.Use(async (context, next) =>
{
RewriteXFrameOptionsHeader(context);
await next();
if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
{
context.Request.Path = "/";
await next();
}
});
app.UseDefaultFiles(new DefaultFilesOptions {DefaultFileNames = new List<string> {"index.html"}});
app.UseStaticFiles();
...
It it doesn't work, because scripts are not loaded. The Angular index.html uses base href and relative paths to js sources, like so:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<base href="/ngxapp">
...
<script src="runtime.7cef1b4acdcbe752.js" type="module"></script><script src="polyfills.e4b5afbd657fbe4a.js" type="module"></script><script src="main.c8a9bcf210ef6760.js" type="module"></script>
</body>
</html>
Even though script src's contain relative paths, the browser tries to load script from the root. Here's what dev tools Network tab shows:
Request URL: https://localhost:7101/runtime.7cef1b4acdcbe752.js
MDN docs for base href state:
The HTML element specifies the base URL to use for all relative URLs in a document.
Why then scripts are being loaded from root?

Forwarded URL renders the webpage in half the height as normal for a Flutter Web app hosted in Firebase

I have a firebase hosted Flutter Web application which is a game. Since the URL for the Firebase hosted site (https://jw-daily.web.app) is difficult to remember for users, I bought a domain name (joinedwords.com) and redirected the URL to the firebase hosted site.
Problem is that when I type the domain URL i.e. joinedwords.com, the website renders in only half the height like below:
However, if I type the original URL (https://jw-daily.web.app) in the browser, the webpage renders in full like below:
All that I have done is with my domain provider, I have set a forward with masking of joinedwords.com => https://jw-daily.web.app/
I looked up all the other solutions around why a webpage is rendering in half. However most of them are asking to make changes to the code and I don't want to do that since the original URL is working fine. Incidentally this issue is happening only on mobile browsers and not happening on desktop. In desktop, the website renders correctly regardless of which URL is typed.
Please suggest if you are aware of how we can solve this problem. Here is my index.html file.
<!DOCTYPE html>
<html prefix="og: http://ogp.me/ns#">
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
-->
<base href="/">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A Daily Word Game">
<meta image="" />
<meta property="og:image:url" content="https://s3.us-east-2.amazonaws.com/joint.words/joined-xxx.png"
property="og:image:secure_url" content="https://s3.us-east-2.amazonaws.com/joint.words/joined-xxx.png"
property ="og:image:alt" content="Joined Words Logo"
property="og:image:type" content="image/png"
/>
<!--
property="og:image:width" content="100"
property="og:image:height" content="100"
-->
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Joined Words">
<link rel="apple-touch-icon" href="https://s3.us-east-2.amazonaws.com/joint.words/joined-256.png">
<!-- Favicon -->
<link rel="shortcut icon" href="favicon.png" type="image/x-icon">
<link rel="icon" href="favicon.png" type="image/x-icon">
<title>Joined Words</title>
<link rel="manifest" href="manifest.json">
<meta name="google-site-verification" content="XXXXXXXXX-XXXXXX" />
/>
<!-- Global site tag (gtag.js) - Google Ads: xxxxxxxxx -->
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-xxxxxxxxxxx"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'AW-xxxxxxxxxxx');
</script>
<!-- Event snippet for Website traffic conversion page -->
<script>
gtag('event', 'conversion', {'send_to': 'xxxxxxxxxxxxxxxxxxx'});
</script>
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
scriptTag.src = 'main.dart.js?version=1';
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}
if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing ?? reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});
// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.',
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</script>
<!-- Initialize Firebase -->
<script src="/__/firebase/9.0.2/firebase-app.js"></script>
<script src="/__/firebase/9.0.2/firebase-analytics.js"></script>
<script src="/__/firebase/init.js"></script>
<!-- Initialize app -->
<script src="main.dart.js?version=15 " type="application/javascript"></script>
</body>
</html>
Found an answer to the issue I was facing. Here is the link to the same:
Bootstrap Responsive Design Fails with Web Forwarding
This is because you are using a framed redirect which essentially loads up the target website in an iFrame. Doing so loses any responsive capabilities. What you are best doing is changing your web forwarding method to actually forward to the new URL using a non-framed redirect. This will then properly load up the target URL in the users browser and all the responsive capabilities that go with it.

Express.js not sending css file

I am setting up a web server with exprss.js and socket.io. I set up a static folder so I can link my stylesheets without having to send every single file. But I am getting this error
Refused to apply style from 'http://localhost:3000/public/styles/index.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
this is my app.js file
const express = require("express");
const app = express();
var server = require('http').Server(app);
var io=require('socket.io')(server);
app.use(express.static('public'));
app.get('/', function (req, res) {
res.sendFile(__dirname + '/public/views/index.html');
});
io.on('connection', function(socket){
socket.emit('chat message', {hello: 'world'});
socket.on('chat message', function (data){
console.log(data);
});
});
server.listen(3000);
the index.html page looks like this
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link type="text/css" rel="stylesheet" href="/public/styles/index.css">
</head>
<body>
<p>yo</p>
</body>
</html>
the file structure is the following
|-public
|-styles
-index.css
|-views
-index.html
-app.js
I think there is something wrong with my server setup
This is my first time using node
You get this error usually when there is no CSS file under that link.
When you use app.use(express.static('public')); directly express serves everything under root endpoint.
So you can use <link type="text/css" rel="stylesheet" href="/styles/index.css">.
If you would like to use /public/xxx.css you can use
app.use('public', express.static('public'));
When you use static, the original folder is not included in the path url, try something like this :
<link type="text/css" rel="stylesheet" href="/styles/index.css">

ASP.NET Core - serve different HTML file for SPA?

Question
How can I serve different HTML (entry) files for an SPA application (Vue) in ASP.NET Core?
Explanation
Depending on a condition, I would like to serve a different HTML page (much like a controller would do for a non-SPA). The page would still include the entry point for Vue apps <div id="app">, but some other changes should be done before serving the HTML.
I know I somehow have to change the startup.cs file because that renders the HTML with app.UseStaticFiles() and app.UseSPAStaticFiles()
Example
Condition 1 is fulfilled, base.html is served from client -> public -> base.html
Condition 2 is fulfilled instead, special.html is served from client -> public -> special.html
Code
The basic HTML looks something like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Title</title>
</head>
<body>
<noscript>
<strong>We're sorry but this webpage doesn't work properly without JavaScript enabled. Please enable it to
continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
The important parts of startup.cs looks like this:
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
// ....
app.UseStaticFiles();
app.UseSpaStaticFiles();
// ....
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
if (env.IsDevelopment())
{
endpoints.MapToVueCliProxy(
"{*path}",
new SpaOptions { SourcePath = "ClientApp" },
npmScript: "serve",
regex: "Compiled successfully");
}
// Add MapRazorPages if the app uses Razor Pages. Since Endpoint Routing includes support for many frameworks, adding Razor Pages is now opt -in.
endpoints.MapRazorPages();
});
// ....
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
});

Inline the Web App Manifest?

I have a marketplace/web application with thousands of static single page apps.
Wish to add a Web App Manifest for each single page app in the <head> </head> tag of their corresponding stem_url (The {root}/index.html for all the urls of a given SPA).
The standard method:
<link rel="manifest" href="/manifest.json">
…does not seem like a good way to go forward because this would mean thousands of manifest.js files being dumped into the /public folder (it's a rails app!) and it would eventually make the app/assets compilation job very heavy as this number goes up.
Is there a way we could inline manifest json just like we do the style tags:
<style>
body { // style here }
…
</style>
An equivalent of manifest declaration:
<manifest>
{
"name": "HackerWeb",
"short_name": "HackerWeb",
…
}
</manifest>
You can inline the json by using a data:url. So instead of the standard
<link rel="manifest" href="/manifest.json">
it would be
<link rel="manifest" href='data:application/manifest+json,{ "name": "theName", "short_name": "shortName", "description": "theDescription"}' />
I wanted to inline it too and tried it just now. It works
Improved answer
As mentioned by RADXack, this works great
<link rel="manifest" href='data:application/manifest+json,{ "name": "theName", "short_name": "shortName", "description": "theDescription"}' />
But what if you want to add more attributes like the colors or start_url?
Then on your server you could add:
const manifest = JSON.stringify({
name: "React Doc",
short_name: "React"
start_url: "/",
background_color: "#fffff",
theme_color: "#ff00ff",
display: "standalone",
});
const HTML = `<!DOCTYPE html>
<html lang="en">
<head>
<link rel="manifest" href='data:application/manifest+json,${encodeURIComponent(manifest)}' />
...rest of your code`
encodeURIComponent will convert all special characters for you.
This way, you are sure that whatever the data being passed is, it'll be URL friendly
The main thing to remember is that the manifest request is still just a network request.
So you can Add Query Params
/manifest.json?title=Hello&icon=.....
Or you could do:
/manifest.json?appId=1234
OR you can just use a pretty URL:
/manifest/1234
Then on your server you can return the JSON that you want.