NextJS Dynamic favicon and title - javascript

Greetins, I'm trying to build a webconstructor. Right now my application works like that:
I check the url the user is on (for example localhost:3000)
I get his project name on my webconstructor (localhost:3000 -> projectName: project1)
I fetch the user's website data (for example favicon and title) (project1: {favicon: 'url', ...}
Is it possible to render the favicon and title before the user enters the page so that it shows the right favicon and title in the browser. Right now I can only get it via useEffect in the main App component (but it's not good for seo). I have tried with getInitialProps but it doesn't seemd to do the job.
Thank you in advance

Yeah it is possible to render dynamic favicon and title. Using the Head component from next/head. You can also create a GenericHead component that you can use whenever you want. Something like:
import Head from 'next/head'
const GenericHead: FC<{ title?: string; favicon?: string }> = (props) => {
const { title, favicon } = props
return (
<Head>
{title ? <title>{title}</title> : <title>Document</title>}
<meta
name='description'
content='your description'
/>
<link rel='icon' href={favicon ? favicon : '/favicon.ico'} />
</Head>
)
}
You can add more things to this GenericHead, like OpenGraph meta tags. And use it whenever you want

Related

Combine/composite two independet vue apps together

I have two vue apps that are developed independently of each other.
User UI
Admin UI
Both have own routes, store, configs, etc.
I found this comment https://forum.vuejs.org/t/composing-multiple-apps-as-a-single-spa/12622/16 which handles each app as a component inside a main app.
I tried and it and got it working till i tried it with my "real" apps.
They fail miserably because they cant resolve paths and missing stuff like the routing.
Main: App.vue
<script>
export default {
name: "MainApp",
data() {
return {
app: "user",
};
},
methods: {
changeApp(name) {
console.log("Change app called", name);
this.app = name;
},
},
};
</script>
<template>
<div>
<UserApp v-if="app === 'user'" #changeApp="changeApp"></UserApp>
<AdminApp v-else-if="app === 'admin'" #changeApp="changeApp"></AdminApp>
<div v-else>Default App ({{ app }})</div>
</div>
</template>
Main: main.js
import { createApp } from "vue";
import Main from "./App.vue";
import UserApp from '../apps/user/src/App.vue';
import AdminApp from '../apps/admin/src/App.vue';
const main = createApp(Main);
main.component("UserApp", UserApp);
main.component("AdminApp", AdminApp);
main.mount("#main");
Main: index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<title>OpenHaus</title>
</head>
<body>
<div id="main"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
Inside the main vue app i have a folder called "apps" which contains the admin & user apps.
Each App.vue file is then imported and handled as a file.
Doing this, a important step is missing: How tho handle from each sub-app the main.js file?
Treating each app as a component for the main app sees not as good as thought as first.
How can i combine the two apps together as a single app, while i maintain/develop/test each app separate?
Perhaps after "compiling" as library: https://vitejs.dev/guide/build.html#library-mode ?
User App: https://github.com/OpenHausIO/frontend
Admin App: https://github.com/OpenHausIO/admin-frontend
Main App: https://github.com/OpenHausIO/frontend-composition
I have personally never heard of a setup where two apps are combined into one. I can understand why this would cause the conflicts that you mention. If I was in your shoes I would probably go with a different approach.
It seems that you have a user and admin app. If the intention is to have a user and admin environment where the content is similar, but certain things can only be accessed if you are an admin. I would create one app that establishes based on your credentials whether you will be in the 'admin' or 'user' environment. Which will be one app that is not split up into two.
If the user and admin apps will be completely different in content, then I would simply create two different apps. This will not only resolve the conflicts, but I think it will also give you a clearer overview of the apps that you are working on. It can become difficult to make the distinction on which of the apps you are making a change when both are combined into one. Especially if you intend to upscale the apps in the future.

React/NextJS passing both children and other arguments to component

I'm using NextJS with a global PageLayout wrapper for all of my pages that sets the head and creates the wrapping divs for the page. However I am now trying to set a custom title tag for each page, which requires me to pass an argument to the PageLayout component with the page title I want to set. However when trying to pass both the PageLayout children and the page_title argument - the page_title property does not get passed in.
Here is what I have for the PageLayout component:
import Head from 'next/head'
import styles from "../../../styles/layout/PageLayout.module.scss"
const PageLayout = ({children, page_title}) => {
console.log(`-------------------------------------------------------`)
console.log(`Page Title: ${page_title}`)
console.log('PAGE Children (NEXT LINE):')
console.log(children)
return (
<div className={styles.container}>
<Head>
<title>{page_title}</title>
<meta name="description" content="JWS Fine Art" />
<link rel="icon" href="/JWS_ICON.png" />
<link rel="preconnect" href="https://fonts.gstatic.com"/>
<link href="https://fonts.googleapis.com/css2?family=Alegreya+Sans+SC:wght#300&family=Lato:wght#300&display=swap" rel="stylesheet"/>
</Head>
<main className={styles.main}>
{children}
</main>
</div>
)
}
export default PageLayout;
And here is an example of how I am passing in the page_title and children for one of my pages:
return (
<PageLayout page_title={"Orders"}>
<div className={styles.main_container}>
<div className={styles.main_body}>
<h2 className={styles.module_title}>Order Management:</h2>
{page_jsx}
</div>
</div>
</PageLayout>
)
And when I try to access that page, I see the following console output from the PageLayout logging:
This all being said, I'm wondering if it is just not possible to pass both children and other arguments? I haven't been able to find any info on this issue online, and no method I have tried has worked, so am coming here. Would greatly appreciate any input anyone might have!
Issue ended up being due to attempting to use "title" as a var name which seems to be a global variable name that can't be used - and also due to me failing to update both the mobile and desktop JSX for the page.
The functionality does work as expected, and you can pass both children and additional props. Thanks to #PrinceAgrawal for making me check my work properly :P
For a working example of this functionality, you can view this example repo:
https://github.com/tsmith165/page_layout_example

How do I load mapbox in Next.js?

I'm using Next.js and their next/script component to load mapbox in a <Map /> component. This seems to work.
Map.js
import Script from 'next/script';
export default Map() {
const createMap = () => {
// set access token
mapboxgl.accessToken = 'xxxxxxxxxxxxxxx...';
// create map
const map = new mapboxgl.Map({...});
}
return (
<>
<Script
onLoad={() => {
createMap();
}}
src="https://api.mapbox.com/mapbox-gl-js/v2.3.1/mapbox-gl.js"
/>
<div id="map"></div>
</>
);
}
1 - If I have multiple instances of <Map /> on a page, will Next.js load this script multiple times?
Is there a better way to load this script once, globally and synchronously? It doesn't seem to work if I load it in my _document.js page. Or is this not an issue?
2 - I also have to load the CSS file. Right now I have it loaded on the page that has the map on it.
pages/contact.js
import Head from 'next/head';
export default function PageContact() {
return (
<Head>
<link href="https://api.mapbox.com/mapbox-gl-js/v2.3.1/mapbox-gl.css" rel="stylesheet" />
</Head>
);
}
If I add maps to other pages, I'd have to include this CSS file on all those other pages, resulting in a lot of duplication. Should I abstract this <Head> to its own component and include that on each page?
Or should I be moving this <Head> component into the <Map> component? It makes sense to centralize this style in one place, but at that point, just like the <Script>, I'm guessing Next.js would load the CSS file each time the component is instantiated, so if a page had more than one map on it, the CSS would be loaded multiple times.
What is the best way to load Mapbox in a Next.js project?

How do I get the base url of the site for twitter:image meta tag?

I have the following front matter in a markdown file.
---
title: "Hello world!"
excerpt: "Lorem ipsum dolor sit amet"
coverImage: "/assets/blog/hello-world/cover.png"
date: "2020-03-16T05:35:07.322Z"
author:
name: Mario
picture: "/assets/blog/authors/mario.png"
ogImage:
url: "/assets/blog/hello-world/cover.png"
---
I require passing the full url of the image to twitter card meta twitter: image and open graph meta property = "og: image"
For this I need to obtain the base url of the site to use it as a prefix to the image path that I obtain through front matter
<Head>
{/* Twitter */}
...
<meta name="twitter:image" content={data.ogImage.url} />
{/* Open Graph */}
...
<meta property="og:url" content={``} key="ogurl" />
<meta property="og:image" content={data.ogImage.url} key="ogimage" />
</Head>
For now data.ogImage.url has the value /assets/blog/hello-world/cover.png but in order to work I need to prefix this output with site base url
How do I get the base url of the site in nextjs?
There are several ways to achieve such a thing:
Create your base URL in env file and refer it by proccess.env (This may help with this approach).
The .env file:
// .env
REACT_APP_BASE_URL=http://example.com/
The index file:
<!-- index -->
<meta property="og:image" content={`${process.env.BASE_URL}${data.ogImage.url}`} key="ogimage" />
Using a relative path, in this approach, you should use image relative path which work like this (this may help with it):
Let's say we have below structure:
--src
|--assets
|--images
|--image.png
--index
If we are in index file we will refer to that image like this:
<meta property="og:image" content={`./src/${data.ogImage.url}`} key="ogimage" />
|__let's say this will return assets/images/image.png
Use javascript built-in methods like window.location. To access the base URL we can get origin property from it like this: window.location.origin (For using this method in next.js this may probably help).
<meta property="og:image" content={`${window.location.origin}${data.ogImage.url}`} key="ogimage" />
NOTE: As #AlexeiLevenkov mentioned in comments since you are asking for the full path of the image, the best way to do it would sticking with the first approach.
Assuming you can use normal client-side JavaScript (I haven't used nextjs before) you can access the base url of the page with window.location.origin
The following screenshot explain everything in itself. If you aren't able to access the window object then probably you are using at a place where it is actually not accessible. You can try writing your short script in tag and can update the meta values. In that script, you'd access to the window object.
During server side rendering you have no window, so you cant get access to location.
But you can leave url prefix empty on server side and define it on client.
As explained in this answer https://stackoverflow.com/a/55151122/615274 Using side effect in the component allows me to access the window object and in consequence to window.location.href the final solution looks similar to this
export default function Post({ post, name }) {
const { data, content } = post;
const [origin, setOrigin] = React.useState("");
React.useEffect(() => {
setOrigin(window.location.origin); // <-- here I get access to window
}, []);
return (
<Layout title={data.title} description={data.excerpt}>
<Head>
{/* Twitter */}
<meta name="twitter:image" content={`${origin}${data.ogImage.url}`} />
{/* Open Graph */}
<meta property="og:image" content={`${origin}${data.ogImage.url}`} key="ogimage"
/>
</Head>
<main>
<PostBody data={data}>{content}</PostBody>
</main>
</Layout>
);
}
In the content attribute of twitter:image and og:image I can set the full url like this `${origin}${data.ogImage.url}`

React - insertion of third party script tag inside render rather than page head causes app failure

I'm writing a React component to use Mozilla's PDF.js project to render a document in HTML. I've gotten it working successfully when the example's Create-React-App project includes
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/1.8.527/pdf.min.js"></script>
in the index.html head block. If I remove it and insert it in the component render method, the application breaks. Is there a way to add a <script> tag to the body of the application
Background
According to the examples, there are two ways you can add a document to a page: render each page with the canvas tag, or use pdfjs-dist/web/pdf-viewer.js
Code
Following the Webpack examples (since this is a React project), I came up with the following code:
import React, {Component} from 'react';
import pdflib from 'pdfjs-dist';
import {PDFJS} from 'pdfjs-dist/web/pdf_viewer'
import 'pdfjs-dist/web/pdf_viewer.css';
export default class PDF extends Component {
constructor(props) {
super(props);
pdflib.PDFJS.workerSrc = require('file-loader!pdfjs-dist/build/pdf.worker');
this.file = props.file;
this.pdfViewer = null;
this._containerId = 'viewer_container';
this.pagesInit = this.pagesInit.bind(this);
}
componentDidMount() {
this._loadDocument();
}
pagesInit(e) {
this.pdfViewer.currentScaleValue = 'page-width';
}
_loadDocument() {
const container = document.getElementById(this._containerId);
this.pdfViewer = new pdflib.PDFJS.PDFViewer({
container: container
});
container.addEventListener('pagesinit', this.pagesInit);
const loadingTask = pdflib.getDocument(this.file);
loadingTask.promise.then((document) => {
this.pdfViewer.setDocument(document);
}).catch((err) => {
console.log(err);
});
}
render() {
return(
<div id={this._containerId}>
<div className="pdfViewer" id="viewer"></div>
</div>
)
}
}
However, for a reason unknown to me, I need to include the build of pdf.js in the head of the index page of the application so it is loaded before the bundle.
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/1.8.527/pdf.min.js"></script>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Without it, the document will not render. This seems directly related to script ordering.
Part of the convenience of an external React component is its self-contained, and this script requirement means this component certainly is not self contained.
I've attempted to use react-async-script-loader to wrap the component in a Higher Order Component and retrieve the script, but the same error happens.
Is there another solution I should look at?

Categories