I have a project which needs to create a dynamic manifest.json file for my PWA (ReactJS).
Please see below my codes:
app.service.js:
function getAppDetails() {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
// TODO create dynamic manifest.json file here
return fetch(`${config.apiUrl}/api/apps`, requestOptions).then(handleResponse);
}
function handleResponse(response) {
return response.text().then(text => {
const data = text && JSON.parse(text);
if (!response.ok) {
if (response.status === 401) {
// auto logout if 401 response returned from api
logout();
location.reload(true);
}
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
return data;
});
}
app.actions.js:
function getAppDetails() {
return dispatch => {
dispatch(request());
appService.getAppDetails()
.then(
details => dispatch(success(details)),
error => dispatch(failure(error.toString()))
);
};
function request() { return { type: appConstants.GETDETAILS_REQUEST } }
function success(details) { return { type: appConstants.GETDETAILS_SUCCESS, details } }
function failure(error) { return { type: appConstants.GETDETAILS_FAILURE, error } }
}
LoginPage.jsx:
import { appActions } from '../_actions';
class LoginPage extends React.Component {
constructor(props) {
super(props);
// reset login status
this.props.dispatch(userActions.logout());
this.state = {
email: '',
password: '',
isDisabled: false,
submitted: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.showSwal = this.showSwal.bind(this);
}
componentDidMount() {
// TODO call the action to add dynamic manifest file to index.html
this.props.dispatch(aapActions.getAppDetails());
}
render() {
return (
................
);
}
}
I am new to this kind of thing. How can I get started?
I have a project which needs to create a dynamic manifest.json file for my PWA (ReactJS).
Please see below my codes:
app.service.js:
function getAppDetails() {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
// TODO create dynamic manifest.json file here
return fetch(`${config.apiUrl}/api/apps`, requestOptions).then(handleResponse);
}
function handleResponse(response) {
return response.text().then(text => {
const data = text && JSON.parse(text);
if (!response.ok) {
if (response.status === 401) {
// auto logout if 401 response returned from api
logout();
location.reload(true);
}
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
return data;
});
}
app.actions.js:
function getAppDetails() {
return dispatch => {
dispatch(request());
appService.getAppDetails()
.then(
details => dispatch(success(details)),
error => dispatch(failure(error.toString()))
);
};
function request() { return { type: appConstants.GETDETAILS_REQUEST } }
function success(details) { return { type: appConstants.GETDETAILS_SUCCESS, details } }
function failure(error) { return { type: appConstants.GETDETAILS_FAILURE, error } }
}
LoginPage.jsx:
import { appActions } from '../_actions';
class LoginPage extends React.Component {
constructor(props) {
super(props);
// reset login status
this.props.dispatch(userActions.logout());
this.state = {
email: '',
password: '',
isDisabled: false,
submitted: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.showSwal = this.showSwal.bind(this);
}
componentDidMount() {
// TODO call the action to add dynamic manifest file to index.html
this.props.dispatch(aapActions.getAppDetails());
}
render() {
return (
................
);
}
}
I am new to this kind of thing. How can I get started?
Share Improve this question edited Aug 18, 2023 at 10:45 Sanjeet kumar 3,4413 gold badges18 silver badges27 bronze badges asked Oct 25, 2018 at 20:09 Erlinda SantiagoErlinda Santiago 311 gold badge1 silver badge2 bronze badges 1- 1 It may be hard for people to help here, since they may not be sure where you are stuck. Did you try the task you describe, and had some sort of difficulty? If you can narrow the issue down, that might help people help you. – halfer Commented Oct 30, 2018 at 20:50
5 Answers
Reset to default 13if you want to create dynamic manifest.json you want to have a link tag in the HTML with rel="manifest" but without href attribute. And use this tag later to populate your manifest. As in
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>title</title>
<link rel="manifest" id="my-manifest-placeholder">
</head>
<body>
<!-- page content -->
</body>
</html>
Use a JSON object to set your manifest
var myDynamicManifest = {
"name": "Your Great Site",
"short_name": "Site",
"description": "Something dynamic",
"start_url": "<your-url>",
"background_color": "#000000",
"theme_color": "#0f4a73",
"icons": [{
"src": "whatever.png",
"sizes": "256x256",
"type": "image/png"
}]
}
const stringManifest = JSON.stringify(myDynamicManifest);
const blob = new Blob([stringManifest], {type: 'application/json'});
const manifestURL = URL.createObjectURL(blob);
document.querySelector('#my-manifest-placeholder').setAttribute('href', manifestURL);
Unfortunately, the advice on using blob:
or data:
URLs fails to work correctly in some cases, because manifest.json
URLs are supposed to be static:
Do not change the name or location of your web app manifest file, doing so may prevent the browser from updating your PWA.
As that article explains, there's a special mechanism for updates to manifest.json
(triggered by very specific conditions) - it is not supposed to be updated via the URL name.
Moreover, Android may no longer recognize the app as a full (WebAPK-installable) PWA, even when the app passes the Lighthouse check. In that case, it's still "installed" as a regular PWA with an ugly Chrome icon in the corner of the app launch icon. I've spend many hours trying to understand why, and finally stumbled on that article with the explanation.
So please either
- Serve dynamic
manifest.json
from a completely static URL, or - Determine
href
based on some attribute (e.g. hostname) that doesn’t change over the lifetime of the app (so that different variations of manifest could be served for different clients, but each client’s version would have a static URL).
Inspired from @Sanjeet kumar's solution for my NextJs application:
// _document.tsx
<link href="/manifest.json" rel="manifest" id="manifest" />
// _app.tsx
import manifest from "../../public/manifest.json";
const manifestElement = document.getElementById("manifest");
const manifestString = JSON.stringify({
...manifest,
start_url: `${homePagePath}${storeCode}`,
});
manifestElement?.setAttribute(
"href",
"data:application/json;charset=utf-8," + encodeURIComponent(manifestString)
);
I was facing some issues with the Blob, It was not working in android as expected. The below answer works fine.
var myDynamicManifest = {
"name": "Your Great Site",
"short_name": "Site",
"description": "Something dynamic",
"start_url": "<your-url>",
"background_color": "#000000",
"theme_color": "#0f4a73",
"icons": [
{
src: "icon_size_36.png",
sizes: "36x36",
type: "image/png",
},
{
src: "icon_size_48.png",
sizes: "48x48",
type: "image/png",
},
{
src: "icon_size_72.png",
sizes: "72x72",
type: "image/png",
},
{
src: "icon_size_96.png",
sizes: "96x96",
type: "image/png",
},
{
src: "icon_size_144.png",
sizes: "144x144",
type: "image/png",
},
{
src: "icon_size_192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "icon_size_512.png",
sizes: "512x512",
type: "image/png",
},
]
}
const link = document.createElement("link");
link.rel = "manifest";
const stringManifest = JSON.stringify(myDynamicManifest);
link.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(stringManifest))
document.head.appendChild(link);
Starting with a <link rel="manifest">
and setting a href from javascript works, with both data:
and blob:
URLs.
Known issues:
- relative paths for
start_url
,scope
and icons won't work. They'll be interpreted as relative to the blob: or data: urls, which is invalid. Symptom:Manifest: property 'start_url' ignored
. Solution: build absolute URL, e.g. for URLs with the same origin as the frontend, I've used
new URL(`/my/absolute/path/?evensomeparameter=${valueVariable}`,
window.location.origin).href
Content-Security-Policy
may prevent the manifest from being loaded fromblob:
ordata:
. Symptom: in the console, you'll find errors statingrefused to load manifest from 'blob:https://my.domain.com/blah' because it violates the following Content Security Policy directive:
. Solution: you can enablemanifest-src blob:
ormanifest-src data:
in the Content Security Policy. This comes either from a response header - see load balancer / CDN / server config - , or from a<meta
head directive on your page https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy- there's an issue last updated in 2022 stating that Firefox on Android only read the manifest on document load, so setting the
href
with a delay won't work https://github.com/mozilla-mobile/fenix/issues/16672
Just for completeness sake, an alternative: take advantage of session data stored in cookies and generate the manifest server-side. But take care of allowed domains - see Content-Security-Policy above. And figure out where you want the static assets like icons and splash screen to be served from. Prefer absolute URLs.
https://medium.com/limehome-engineering/create-a-pwa-app-manifest-dynamically-spa-angular-1627260e0390