i am updating dependencies for a client and i updated nextJS from getInitialProps to getServerSideProps.
Therefore, i had to update next-i18next.
Before, here's what was used in getInitialProps for translations :
class Homepage extends React.Component {
static async getInitialProps ({ store }) {
store.dispatch(PlansCreators.plansGet())
return {
namespacesRequired: ['common', 'containers', 'home', 'form']
}
}
render () {
return (
<Layout>
<Head>
<title>{`${config.DEV ? 'DEV - ' : ''} ${t('SEO.BASE.TITLE')}`}</title>
<meta name='description' content={t('SEO.BASE.DESCRIPTION')} />
<meta property='og:title' content={`${t('SEO.BASE.TITLE')}`} />
<meta property='og:description' content={t('SEO.BASE.DESCRIPTION')} />
<meta property='og:image' content='' />
</Head>
<Home />
</Layout>
)
}
}
After my update :
function Homepage() {
return (
<Layout>
<Head>
<title>{`${config.DEV ? 'DEV - ' : ''} ${t('SEO.BASE.TITLE')}`}</title>
<meta name="description" content={t('SEO.BASE.DESCRIPTION')} />
<meta property="og:title" content={`${t('SEO.BASE.TITLE')}`} />
<meta property="og:description" content={t('SEO.BASE.DESCRIPTION')} />
<meta property="og:image" content="" />
</Head>
<Home />
</Layout>
)
}
export async function getInitialProps({ locale, store }) {
store.dispatch(PlansCreators.plansGet())
return {
props: {
...(await serverSideTranslations(locale, ['common']))
}
}
}
The translations seems to be working during the render, but only for like 0.5 seconds. Then, it come back to SEO.BASE.TITLE... So i thought it was the localePath in the next-i18next.config.js that was involved, but that's not the case.
Here's my next-i18next.config.js :
module.exports = {
i18n: {
locales: ['fr', 'en'],
defaultLocale: 'fr',
reloadOnPrerender: process.env.NODE_ENV === 'development',
debug: process.env.NODE_ENV === 'development',
localePath:
typeof window === 'undefined'
? require('path').resolve('./public/locales')
: '/locales'
}
}
I also have this error in my javascript console (browser side) :
react_devtools_backend.js:4012 i18next: hasLoadedNamespace: i18next was not initialized
and
i18next::translator: key "SEO.ACCOUNT.TITLE" for languages "fr" won't get resolved as namespace "common" was not yet loaded This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!
Do i miss something ? Do i have to build a backend even with next-i18next ? I just follow the documentation 3 times...
Here are my versions :
"i18next": "^22.0.6",
"react-i18next": "^12.0.0",
"next-i18next": "^13.0.3",
Here's how i wrap my App in _app.tsx
export default compose(
withRedux(bootstrapStore),
withReduxSaga,
withRouter
)(appWithTranslation(MyApp, nextI18NextConfig))
Thanks for help.
Related
I'm trying to fetch data from the server side using SSR. unfortunately the initial loading time is awful, around 6s.
i tried to use both getServerSideProps and getStaticProps. however, the results remains the same.
it there a way to bypass that or to call the API from the FE would suit the situation better?
import Head from 'next/head';
import { useQuery } from 'react-query';
import { getCountries } from '../infrastructure/http/client';
import { Navbar } from '#/components/navbar/navbar';
import { FilterSection } from '#/components/filter-section/filter-section';
import { CardsSection } from '#/components/cards-section/cards-section';
import { Store } from '#/state/store';
export async function getServerSideProps(context : any) {
const getCountriesData = await getCountries();
const countriesData = await JSON.parse(getCountriesData);
return {
props: {
countriesData,
},
};
};
// Maybe i dont need an api call for the entire page, will try to move it to the
// store
export default function Home({countriesData} : any) {
// pass the data received from getServerSideProps
const { data } = useQuery('todos', getCountries, { initialData: countriesData });
console.log(data);
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Navbar />
<Store >
{/* TODO ---- */}
{/* Routing */}
<FilterSection />
<CardsSection countriesData={countriesData} />
</Store>
</>
);
};
I am getting a windows is undefined error while using react-sweet-state with nextjs.
I have seen similar nextjs issues and the recommended solution was to use a rendering condition typeof window === undefined. I cannot use that rendering condition because I need the app to load even when the window is undefined.
Is there any other way to resolve the issue ?
Edit: I have added some code here which may help
pages/article/[id].js
const Article = () => {
// here is where i get the 'windows is undefined' error
const [{ article }, { fetchArticleData }] = useArticleStore();
return (
<>
<Head>
<meta property="og:title" name="og:title" content={article.title} />
<meta property="og:description" name="og:description" content={article.desc} />
</Head>
<article>
<h1>{ article.title }</h1>
<p
dangerouslySetInnerHTML={{ __html: article.content }}
></p>
</>
)
}
pages/_app.js
import "antd/dist/antd.css";
import "../styles/globals.scss";
import Layout from "../layout";
const App = ({ Component, pageProps }) => {
return (
<Layout>
<div suppressHydrationWarning>
{typeof window === "undefined" ? null : <Component {...pageProps} />}
</div>
</Layout>
);
};
export default App;
I'm using Razzle for using React and Server Side Rendering with React Helmet. I have this problem when you use React Helmet to set meta tags with dynamic values, it's not displayed correctly. But it works if you set the meta tags with static values.
Please take a look at some codes.
SEO.js Component
import React, { Component } from 'react';
import { Helmet } from "react-helmet-async";
class SEO extends Component {
constructor(props) {
super(props);
this.state = {
title: this.props.title,
description: this.props.description,
image: this.props.image
}
}
shouldComponentUpdate(nextProps) {
if(this.props != nextProps) {
this.setState({
title: nextProps.title,
description: this.props.description,
image: nextProps.image
})
return true;
} else {
return false;
}
}
render() {
return (
<div>
<Helmet>
<title>{this.state.title ? this.state.title : "Volunteer Hub by Indorelawan"}</title>
<meta name="title" content={this.state.title ? this.state.title : "Volunteer Hub by Indorelawan"} />
<meta
name="description"
content={this.state.description ? this.state.description : "Volunteer Hub by Indorelawan adalah tempat kolaborasi antara relawan dan komunitas sosial yang memiliki semangat kerelawanan dan gotong royong untuk Indonesia."}
/>
<meta
property="og:title"
content={this.state.title ? this.state.title : "Volunteer Hub by Indorelawan"}
/>
<meta
property="og:description"
content={this.state.description ? this.state.description : "Volunteer Hub by Indorelawan adalah tempat kolaborasi antara relawan dan komunitas sosial yang memiliki semangat kerelawanan dan gotong royong untuk Indonesia."}
/>
<meta
property="og:image"
content={this.state.image ? this.state.image : "https://volunteerhub.id/assets/logo/seo.jpg"}
/>
<meta property="og:url" content="https://volunteerhub.id" />
<meta
name="twitter:title"
content={this.state.title ? this.state.title : "Volunteer Hub by Indorelawan"}
/>
<meta
name="twitter:description"
content={this.state.description ? this.state.description : "Volunteer Hub by Indorelawan adalah tempat kolaborasi antara relawan dan komunitas sosial yang memiliki semangat kerelawanan dan gotong royong untuk Indonesia."}
/>
<meta
name="twitter:image"
content={this.state.image ? this.state.image : "https://volunteerhub.id/assets/logo/seo.jpg"}
/>
<meta name="twitter:card" content="summary_large_image" />
</Helmet>
</div>
);
}
}
export default SEO;
Here is the example of setting up the static meta tags:
import React, {Component} from "react";
import SEO from "../../components/SEO";
class ScheduleContainer extends Component {
constructor(props) { super(props); }
render() {
return(
<div>
<SEO
title="Cek Jadwal | Volunteer Hub by Indorelawan"
description="Cek jadwal kegiatan di Volunteer Hub! Volunteer Hub by Indorelawan adalah tempat kolaborasi antara relawan dan komunitas sosial yang memiliki semangat kerelawanan dan gotong royong untuk Indonesia." />
</div>);
}
}
And here is the example of setting up the dynamic meta tags:
import React, {Component} from "react";
import axios from "axios";
import SEO from "../../components/SEO";
class EventContainer extends Component {
constructor(props) {
super(props);
this.state = {
event: {}
}
}
componentDidMount() {
axios.get('API_URL')
.then(response => {
this.setState({ event: response.data.result })
});
}
render() {
return(
<div>
<SEO
title={this.state.event.title}
description={this.state.event.description} />
</div>);
}
}
Server.js
import RootContainer from "./containers/RootContainer";
import React from "react";
import { StaticRouter } from "react-router-dom";
import express from "express";
import { renderToString } from "react-dom/server";
import { Helmet, HelmetProvider } from "react-helmet-async";
const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);
const server = express();
server
.disable("x-powered-by")
.use(express.static(process.env.RAZZLE_PUBLIC_DIR))
.get("/*", (req, res) => {
const context = {};
const helmetContext = {};
const markup = renderToString(
<HelmetProvider context={helmetContext}>
<StaticRouter context={context} location={req.url}>
<RootContainer />
</StaticRouter>
</HelmetProvider>
);
const { helmet } = helmetContext;
if (context.url) {
res.redirect(context.url);
} else {
res.status(200).send(
`<!doctype html>
<html lang="">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta
name="keywords"
content="volunteer, hub, by, indorelawan, volunteer hub, volunteer hub by indorelawan, kolaborasi, dimulai, dari, sini, ubah, niat, baik, jadi, aksi, baik, hari, ini"
/>
<meta name="robots" content="index, follow" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="language" content="Indonesia" />
<meta name="author" content="Indorelawan" />
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="theme-color" content="#222222" />
${helmet.title.toString()}
${helmet.meta.toString()}
${
assets.client.css
? `<link rel="stylesheet" href="${assets.client.css}">`
: ""
}
${
process.env.NODE_ENV === "production"
? `<script src="${assets.client.js}" defer></script>`
: `<script src="${
assets.client.js
}" defer crossorigin></script>`
}
...
</head>
<body>
<div id="root">${markup}</div>
<script>
if ("serviceWorker" in navigator) {
if (navigator.serviceWorker.controller) {
console.log("[PWA Builder] active service worker found, no need to register");
} else {
// Register the service worker
navigator.serviceWorker
.register("pwabuilder-sw.js", {
scope: "./"
})
.then(function (reg) {
console.log("[PWA Builder] Service worker has been registered for scope: " + reg.scope);
});
}
}
</script>
</body>
</html>`
);
}
});
export default server;
Now that you have seen the code, here is the result when I copy pasted to Google SERP Simulator and WhatsApp:
Static meta tags result from Schedule Page:
Google SERP Simulator
WhatsApp
Dynamic meta tags result from Event Page:
Google SERP Simulator
WhatsApp
From the result, it always return the default title and description tag not the title and description passed from the axios. Is is normal behavior or am I doing something wrong?
Razzle IS server side rendering, the problem with your dynamic meta tags scenario is that you're relying on data that is fetched in componentDidMount, and componentDidMount, as a lifecycle method from the commit phase, is not called on the server since there's no actual mounting on server side.
NextJS solves this problem for you because of getInitialProps, which is invoked on server and client.
Turns out Razzle is not Server Side Rendering. Either you have to define the SEO tags using a custom express server, or just use SSR for React.
I am using NextJS for this and this is a no problem.
I'm trying to set up SSR for my React app, which uses Apollo Client for graphql requests to an API. I've set up the webpack config, and the Express app, correctly, and I can see the components getting loaded. The issue here is that getDataFromTree is not waiting for my query at all. It simply returns the component with loading: true and then nothing. I set ssrForceFetchDelay as well to make the client refetch the queries, but nothing.
I keep getting window.__APOLLO_STATE__={}. The components are very basic, making simple queries and displaying the result.
This is my express app:
app.use((req, res) => {
const client = new ApolloClient({
ssrMode: true,
link: new HttpLink({
uri: 'http://localhost:8000/graphql',
fetch: fetch
}),
cache: new InMemoryCache(),
});
const app = (
<ApolloProvider client={client}>
<Router location={req.url} context={{}}>
<App client={client}/>
</Router>
</ApolloProvider>
);
const jsx = extractor.collectChunks(app); // Using loadable
renderToStringWithData(jsx).then((dataResp) => {
const content = dataResp;
const state = client.extract(); // Always {}
const helmet = Helmet.renderStatic();
const html = ReactDOMServer.renderToStaticMarkup(
<Html content={content} helmet={helmet} assets={assets} state={state} />,
);
res.status(200); res.send(`<!doctype html>${html}`);
res.end();
}).catch(err => {
console.log(`Error thrown`);
});
});
app.listen(port, () => {
console.log(`Server listening on ${port} port`);
});
This is my client-side Apollo config:
const cache = new InMemoryCache().restore(window.__APOLLO_STATE__);
const client = new ApolloClient({
ssrForceFetchDelay: 300,
link: links,
cache: cache,
connectToDevTools: true,
});
My App :
const Wrapped = () => {
console.log(`Inside Wrapped`);
return (
<ApolloProvider client={apolloClient}> //The client one
<Router>
<App /> /*Only returns 2 basic components */
</Router>
</ApolloProvider>
);
};
hydrate(<Wrapped />, document.getElementById('root'));
I can see the components on the page, but no queries have been loaded and APOLLO_STATE is always empty. I've been following the documentation on react-apollo to set this up, but I can't understand why this basic set up won't work. Any ideas on what I need to change here? Thanks!
Html component:
import React from 'react';
const Html = ({ content, helmet, assets, state }) => {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<link rel="manifest" href="/manifest.json" />
<link rel="shortcut icon" href="/favicon.ico" />
{helmet.meta.toComponent()}
{helmet.title.toComponent()}
{assets.css &&
assets.css.map((c, idx) => (
<link key={idx} href={c} rel="stylesheet" />
))}
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" dangerouslySetInnerHTML={{ __html: content }} />
<script
dangerouslySetInnerHTML={{
__html: `window.__APOLLO_STATE__=${JSON.stringify(state).replace(
/</g,
'\\u003c',
)};`,
}}
/>
{assets.js &&
assets.js.map((j, idx) => (
<script key={idx} src={j} />
))}
</body>
</html>
);
};
export default Html;
EDIT: Added my HTML component code
Link to repo with basic code that is working correctly. I intend to add Loadable to this to see if that is what is causing the issue.
https://github.com/kvnam/gql-ssr-test
I am using Gatsby.js to create a blog. One of the starter packages seems to leverage an alias, so one can fetch and reuse an image to serve as a favicon. The favicon (alias) is in the src/ folder and it's called favicon.png.
That file is being rendered in this component:
import React from 'react';
import favicon from './favicon.png';
let inlinedStyles = '';
if (process.env.NODE_ENV === 'production') {
try {
/* eslint import/no-webpack-loader-syntax: off */
inlinedStyles = require('!raw-loader!../public/styles.css');
} catch (e) {
/* eslint no-console: "off" */
console.log(e);
}
}
export default class HTML extends React.Component {
render() {
let css;
if (process.env.NODE_ENV === 'production') {
css = <style id="gatsby-inlined-css" dangerouslySetInnerHTML={{ __html: inlinedStyles }} />;
}
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{/* Mobile Meta */}
<meta name="HandheldFriendly" content="True" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{/* Styles'n'Scripts */}
<link href="https://fonts.googleapis.com/css?family=Scope+One" rel="stylesheet" />
{this.props.headComponents}
<link rel="shortcut icon" href={favicon} />
{css}
</head>
<body>
<div id="___gatsby" dangerouslySetInnerHTML={{ __html: this.props.body }} />
{this.props.postBodyComponents}
</body>
</html>
);
}
}
This is what is rendered:
Can someone suggest an alternative way to get it to work?
Thanks in advance!
To add favicon in gatsby you should use gatsby-plugin-favicon :
Install
npm install --save gatsby-plugin-favicon
Use
Add your favicon.png in src folder (or subfolder).
In your gatsby-config.js add the plugin :
plugins: [
{
resolve: `gatsby-plugin-favicon`,
options: {
logo: "./src/path/to/your/favicon",
injectHTML: true,
icons: {
android: true,
appleIcon: true,
appleStartup: true,
coast: false,
favicons: true,
firefox: true,
twitter: false,
yandex: false,
windows: false
}
}
}
]
The plugin recommends a size of 1500x1500px for the favicon.
Doc here : https://github.com/Creatiwity/gatsby-plugin-favicon
Try using a template literal:
link={[
{ rel: 'shortcut icon', type: 'image/png', href: `${favicon}` }
]}
This worked for me (I am using react-helmet though).
See the following tutorials for more help:
https://www.michaelmanges.com/blog/add-a-favicon-to-your-gatsby-website/
https://atrost.com/posts/add-favicon-gatsby/