I'm working on a project using new Next.js 13 app Dir, I'm trying to render the data from CMS. I'm getting the data works but no dynamic value tags are passed to social share cards (Twitter, Facebook) i think because data get rendered in the client, or is it something else!
So, I'm seeking help to understand how to properly fetch and render the data (SSR) way, and what I do wrong in my code.
'use client'
import { useEffect, useState } from 'react'
import { usePathname } from 'next/navigation'
import DefaultTags from '../../../components/DefaultTags'
import { client } from '../../../utils/contentful'
export async function generateStaticParams() {
const res = await client.getEntries({
content_type: 'article',
})
return res.items.map((item) => ({
slug: item.fields.slug,
}))
}
export default function Head({ params }) {
const pathname = usePathname()
const [data, setData] = useState(null)
useEffect(() => {
async function getEntry() {
const response = await client.getEntries({
content_type: 'article',
'fields.slug': params.slug,
include: 10,
})
setData(response.items[0])
}
getEntry()
}, [params.slug])
if (data === null) {
return undefined
}
const { title, category, tags, featuredImg } = data.fields
const url = `https://example.com${pathname}` || 'https://example.com'
return (
<>
<DefaultTags />
<title>{`${title} | blog `}</title>
<meta property="url" content={url} />
<link rel="canonical" href={url} />
<meta name="description" content={title} />
<meta name="keywords" content={tags} />
<meta name="article:section" content={category} />
<meta name="twitter:title" content={`${title} | blog `} />
<meta name="twitter:description" content={title} />
<meta
name="twitter:image"
content={`https:${featuredImg[0].fields.file.url}`}
/>
<meta name="twitter:image:alt" content={title} data-rh="true" />
<meta property="og:title" content={`${title} | blog`} />
<meta property="og:url" content={url} />
<meta property="og:type" content="article" />
<meta property="og:description" content={title} />
<meta
property="og:image"
content={`https:${featuredImg[0].fields.file.url}`}
/>
<meta property="fb:app_id" content="Facebook id" />
</>
)
}
I'm trying to figure out how to add a favicon file to a next.js app (with react 18).
I have made a _document page that has a head tag as follows:
import * as React from "react"
// import {createRoot} from 'react-dom/client'
import { ColorModeScript } from "#chakra-ui/react"
import Document, { Head, Html, Main, NextScript } from "next/document"
import Favicon from "../components/Favicon"
export default class AppDocument extends Document {
static getInitialProps(ctx: any) {
return Document.getInitialProps(ctx)
}
render() {
return (
<Html lang="en">
<Head>
<meta name="theme-color" key="theme-color" content="#000000" />
<meta name="description" content="name" key="description" />
<meta property="og:title" content="title goes here" key="title" />
<meta property="og:description" content="description goes here" key="og:description" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<Favicon />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
I then made a component called Favicon with:
import React from "react";
const Favicon = (): JSX.Element => {
return (
<React.Fragment>
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
</React.Fragment>
)
}
export default Favicon;
I then made a root/packages/src/public folder (this folder is at the same level and place as the pages folder that has the _document.tsx file) and saved each of the assets to it.
I don't get an error, but the favicon does not populate in the browser tab.
How can I add a favicon in nextjs?
I also tried removing the Favicon component and moving the meta tags directly to app.tsx. It still doesnt render the favicon.
I can see from the console errors that the files are not found. They are all saved in the project at public/[file name]. Public is a folder at the same level as the pages directory.
I have working a working favicon in a Next.js 12 + React 18 app by adding the icon link directly to the <Head> and the images in public/[file name].
render() {
return (
<Html lang="en">
<Head>
<link rel="shortcut icon" href="/favicon.ico" />
I assume that your React component is not working because the browser is trying to load the favicon before the page has been hydrated with any Javascript that can generate your React component favicon.
I solved this problem. You need to add the image to the public directory. Next engine will detect this favicon automatically.
but if you need to add special image or etc, you must add image file into public/static folder. and your url should be start with static/*. like this:
<React.Fragment>
<link rel="icon" sizes="76x76" href="static/apple-touch-icon.png" />
</React.Fragment>
And finally, to display the icon in the browser tab, you must perform a hard refresh Ctrl+F5;
You should just add/replace the icon file with the name favicon.ico in the root of the public folder with your icon. If you want to do SEO consider using "next-seo" package
What I do, I create a component say Meta.tsx and use it inside my Pages
import { NextSeo } from "next-seo"
import type { OpenGraph } from "next-seo/lib/types"
import { useRouter } from "next/router"
import type { MetaProps } from "../../types/content"
export const Meta = ({
type = "website",
siteName = "My-site-name",
data,
}: MetaProps): React.ReactElement => {
const router = useRouter()
// TODO Make generator based on different data
const seoData = data
const locale = "en_EN"
const baseURL = process.env.BASE_URL || window.location.origin
const currentURL = baseURL + router.asPath
const seo = {
title: seoData.title,
titleTemplate: `%s - ${siteName}`,
defaultTitle: siteName,
canonical: currentURL,
}
const openGraph: OpenGraph = {
title: seoData.title,
type,
locale,
url: currentURL,
site_name: siteName,
images: seoData.images,
}
const metaLinks = [
{
rel: "icon",
type: "image/svg+xml",
href: "/favicon.svg",
},
{
rel: "apple-touch-icon",
href: "/touch-icon-ipad.jpg",
sizes: "180x180",
},
{
rel: "mask-icon",
type: "image/svg+xml",
color: "#0d2e41",
href: "/favicon.svg",
},
{
rel: "icon",
href: "/favicon.ico",
},
]
return (
<NextSeo
{...seo}
openGraph={openGraph}
additionalLinkTags={metaLinks}
noindex={data.requireAuth || data.noIndex}
/>
)
}
I think you are just missing the correct rel attribute. This works in my project:
import {Html, Head, Main, NextScript} from 'next/document';
export default function Document() {
return (
<Html lang="en">
<Head>
<link rel="shortcut icon" href="/site/images/favicon.ico" />
...
where /site from /site/images/favicon.ico is in the public folder.
import Head from "next/head";
<Head>
// If favicon path is public/images/
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />
</Head>
Then you can render this inside Header component or Lay out component. this
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" /> should work also in _document page if you pass correct `href
The easiest way is to just put the <Head> in your layout component.
import Head from "next/head"
const Layout = ({ children, home }: Props) => {
return (
<>
<div className={styles.container}>
<Head>
<link rel="icon" type="image/svg" href="/icons/YOURICON.svg" />
....rest of your <meta> tags for SEO ....
</Head>
....rest of your html/jsx like page header/main-stage/footer/etc..
)}
Make sure that your icon is in a correct format, put in a folder the image named icon.png and use imagemagick to convert it to .icon format
brew install imagemagick
convert icon.png -scale 16 tmp/16.png
convert icon.png -scale 32 tmp/32.png
convert icon.png -scale 48 tmp/48.png
convert icon.png -scale 128 tmp/128.png
convert icon.png -scale 256 tmp/256.png
convert tmp/16.png tmp/32.png tmp/48.png tmp/128.png tmp/256.png icon.ico
Then i use to generate a component for the initial head content HeadContent.js
const HeadContent = () => (
<>
<link
rel="shortcut icon"
sizes="16x16 24x24 32x32 48x48 64x64"
href="/favicon.ico"
/>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta content="yes" name="apple-mobile-web-app-capable" />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
</>
);
export default HeadContent;
Now in _document.js you can use the HeadContent.js:
import Document, { Html, Head, Main, NextScript } from "next/document";
import HeadContent from "#components/HeadContent";
export default class extends Document {
render() {
return (
<Html lang="es-co">
<Head>
<meta charSet="UTF-8" />
<HeadContent />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
inside pages/_app.tsx
import Head from "next/head";
<Head>
<link rel="icon" href="/favicon.ico" />
</Head>
Running into an issue with getting my meta tags working properly with NextJS.
Here is an online page : https://www.acaciapp.com/action/alimentation/eviter-boire-jus-fruits-presses/O4tkFjvta1c1B8XzFC3j
The info is populated on the head tag but is not parsed by social media (using https://www.opengraph.xyz/ for example) => it does not return the title, nor the description, nor the og:image.
Here is my code :
_app.js
export default function MyApp(props) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
return (
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
<meta name="author" content="Maxime Courtaigne" />
<title key="title">acacia.</title>
</Head>
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<AuthProvider>
<ActionProvider>
<ObjectiveProvider>
<PointsProvider>
{/* <PercentProvider> */}
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
{/* </PercentProvider> */}
</PointsProvider>
</ObjectiveProvider>
</ActionProvider>
</AuthProvider>
</ThemeProvider>
</StyledEngineProvider>
</CacheProvider>
);
}
_document.js :
export default class MyDocument extends Document {
render() {
return (
<Html lang="fr">
<Head>
{/* PWA primary color */}
<meta name="theme-color" content={theme.palette.primary.main} />
<link rel="shortcut icon" href="/favicon.ico" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Caveat:wght#700&family=Karla:wght#300;400;600;800&display=swap"
/>
{/* Inject MUI styles first to match with the prepend: true configuration. */}
{this.props.emotionStyleTags}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
A specific page :
function Action({actionData, actionId, params}) {
// some functions, etc..
return (
<div className={styles.actionPage}>
<Meta
title={`${actionData.title} | acacia`}
desc={actionData.description.substring(0,150)}
canonical={`https://www.acaciapp.com/action/${params.category}/${params.slug}/${params.id}`}
/>
// Some content
</div>
}
The Meta component :
// Header of every page
import Head from "next/head"
import Script from "next/script"
function Meta(props) {
return (
<Head>
<title >{props.title}</title> {/* 40-50 char */}
<meta name="description" content={props.desc} /> {/* 150-160 char */}
<meta property="og:type" content="website" />
{props.canonical && <link rel="canonical" href={`${props.canonical}`} key="canonical" />}
<meta name="og:title" property="og:title" content={props.title} />
<meta name="og:description" property="og:description" content={props.desc} />
<meta property="og:site_name" content="acacia" />
<meta property="og:url" content={`${props.canonical}`} />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content={props.title} />
<meta name="twitter:description" content={props.desc} />
<meta name="twitter:site" content="#acacia_app" />
<meta name="twitter:creator" content="#acacia_app" />
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/favicon.ico" />
{props.image ?
(<meta property="og:image" content={`${props.image}`} />) :
(<meta property="og:image" content="https://firebasestorage.googleapis.com/v0/b/acacia-44ebf.appspot.com/o/ogImages%2Fog-home.png?alt=media&token=81fc5d4e-db1b-4cd2-93ff-cbb27f8b8753" />)}
{props.image && <meta name="twitter:image" content={`${props.image}`} />}
{props.robot && <meta name="robots" content={props.robot} />}
{props.css && <link rel="stylesheet" href={`${props.css}`}/>}
{props.js && <Script type="text/javascript" src={`${props.js}`} />}
</Head>
)
}
export default Meta
Is it an overide issue with __document or __app ? should I add "key" tags ?
Thanks !
----- EDIT -----
I think the issue comes from the __document initial rendering method that is quite inunderstandable for me :/. Here is the code :
import * as React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import createEmotionServer from '#emotion/server/create-instance';
import theme from '../utility/theme';
import createEmotionCache from '../utility/createEmotionCache';
export default class MyDocument extends Document {
render() {
return (
<Html lang="fr">
<Head>
{/* PWA primary color */}
<meta charSet="utf-8" />
<meta key="robots" name="robots" content="index, follow" />
<meta key="themeColor" name="theme-color" content={theme.palette.primary.main} />
<link key="shortcutIcon" rel="shortcut icon" href="/favicon.ico" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Caveat:wght#700&family=Karla:wght#300;400;600;800&display=swap"
/>
{/* Inject MUI styles first to match with the prepend: true configuration. */}
{this.props.emotionStyleTags}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with static-site generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
// Resolution order
//
// On the server:
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. document.getInitialProps
// 4. app.render
// 5. page.render
// 6. document.render
//
// On the server with error:
// 1. document.getInitialProps
// 2. app.render
// 3. page.render
// 4. document.render
//
// On the client
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. app.render
// 4. page.render
const originalRenderPage = ctx.renderPage;
// You can consider sharing the same emotion cache between all the SSR requests to speed up performance.
// However, be aware that it can have global side effects.
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) =>
function EnhanceApp(props) {
return <App emotionCache={cache} {...props} />;
},
});
const initialProps = await Document.getInitialProps(ctx);
// This is important. It prevents emotion to render invalid HTML.
// See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map((style) => (
<style
data-emotion={`${style.key} ${style.ids.join(' ')}`}
key={style.key}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: style.css }}
/>
));
return {
...initialProps,
emotionStyleTags,
};
};
Found out the solution. It has nothing to do with the render method within __document nor __app.
The getStaticProps and getServerSideProps was actually not rendering the proper HTML because of a conditional rendering in the dynamic page.
I was doing :
if (loadingUser) return <Loader/>
return (
// main render of the page
)
By deleting this conditionnal rendering, everything is working perfectly
I'm trying to show different navigations based on if it is the "Homepage" or if it is a page that contains "docs" (e.g. docs/1, docs/2, docs/3) in its URL.
I'm just getting blanks on the "docs"-case - it feels like I am missing something here?
Here's my code so far:
const router = useRouter()
const path = router?.asPath
if (path === '/docs/') {
return (
<>
<Head>
<title>{pageTitle}</title>
{description && <meta name="description" content={description} />}
</Head>
<Layout
navigation={navigation1}
title={title}
tableOfContents={tableOfContents}
>
<Component {...pageProps} />
</Layout>
</>
)
}
if (path === '/') {
return (
<>
<Head>
<title>{pageTitle}</title>
{description && <meta name="description" content={description} />}
</Head>
<Layout
navigation={navigation}
title={title}
tableOfContents={tableOfContents}
>
<Component {...pageProps} />
</Layout>
</>
)
}
NextJS has a built-in route system. You can just create a docs.js file in the pages folder. You can either create different navigations directly in /pages/docs.js or a dynamic component for it.
Here is more about routing https://nextjs.org/docs/routing/introduction
Hope this answers your question :)
I'm not sure if you have fixed this but I have completed the same method with the following code:
import { useRouter } from 'next/router';
const ComponentName = () => {
const router = useRouter();
const { pathname } = router;
if (pathname.includes('/')) {
// Add home component
} else {
// Add another component instead
}
}
export default ComponentName
You can then use conditional statements to check what the path is just like you were doing.
- Tested with Next JS V13
I know this is a duplicate, but I couldn't solve my problem with those answers. so here is my code:
//_document.tsx
import Document, { Html, Head, Main, NextScript } from "next/document";
export default class MyDocument extends Document {
render() {
return (
<Html>
<Head />
<body dir="rtl">
<Main />
<NextScript />
</body>
</Html>
);
}
}
//_app.tsx
import "../styles/globals.scss";
import Head from "next/head";
import type { AppProps } from "next/app";
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Head>
<title>Bitbarg</title>
<meta name="description" content="Descriptions" />
<link rel="icon" href="/favicon.ico" />
<body dir="rtl" />
</Head>
<Component {...pageProps} />
</>
);
}
export default MyApp;
I'm still getting this error.
I tried taking all _app.tsx Head tag in _document.tsx, this removed the error but I'm getting new error as:
Titles should be defined at the page-level using next/head.
I tried only putting code bellow in _app.tsx, but it gives me the next-head-count error again.
<Head>
<title>Bitbarg</title>
</Head>
Resolved when I removed
from _app.js as I was using it already in _document.js