One-sentence summary
When I have await serverSideTranslations
inside getStaticProps
, loading
value from useSession()
remains stuck as true
.
Context
I use next-auth.js
(v3) for auth. I have (gratefully) used this for a year without a problem.
const [session, loading]
= useSession()
is a hook to check if someone is signed in.
The
loading
option indicates the session state is being updated; it istrue
when session data is being fetched andfalse
when it is done checking. https://stackoverflow.com/a/63191786/870121
I'm adding next-i18next
to my project. Most of it's working, but I'm having some difficulty.
I've followed the instructions and example code for next-i18next
.
It works well on pages with getServerSideProps
: pages load, next-auth's useSession()
still works, and translations work without warning or errors (example).
For example, /login
uses getServerSideProps
like this:
export const getServerSideProps = async (context) => {
const { locale } = context;
console.log("Have locale, ", locale);
return {
props: {
csrfToken: await csrfToken(context),
...(await serverSideTranslations(locale, ["common", "login"])),
},
};
};
and works well for /de/login and /en/login or just /login
You might spy the word "loading…" briefly appear in the top-left of the nav bar (on desktop) on those two pages. This appears while loading
is true, but then it quickly disappears.
Problem
I'm running into some difficulty when I use it with getStaticProps
. Specifically, loading
returned by useSession()
remains true
.
Case A: When I have this code at page-level, translations work, but 'loading' value useSession() remains true
export const getStaticProps = async ({ locale }) => {
const mood = "determined";
return {
props: {
mood,
...(await serverSideTranslations(locale, ["common", "test-page"])),
},
};
};
Note the use of await
before serverSideTranslations
there; that's deliberate, as advised in all the docs I've seen.
The above causes this line to not work as expected; loading
stays stuck on true
… hence I don't know if the user is logged in.
import { useSession } from "next-auth/client";
const MyComponent = ()=>{
const [session, loading] = useSession();
return <div>loading is {loading ? "true" : "false"}</div>
}
Something about the await serverSideTranslations
is causing useSession()
to always return [undefined, true]
.
Note: I'm using next-auth v3, not v4.
Case B:
I can remove the await
keyword before serverSideTranslations
, and then useSession()
works as normal… but then translations don't work.
Any tips?
What I've tried so far:
I've focused on trying to adapt next-i18next
to not need the await
keyword.
That took me down a rabbit hole of successive warnings/errors, each caused by the previous fix:
- Remove "await" before
serverSideTranslations
ingetStaticProps
. - Try adding
react: { useSuspense: false }
to next-i18next.config.js per https://stackoverflow.com/a/69311205/870121 (didn't help). - Get error
react-i18next:: You will need to pass in an i18next instance by using initReactI18next
. → Try fixing by addinguse: [initReactI18next],
tonext-i18next.config.js
. - Get error, "appWithTranslation called without config" → fix by adding to
_app.js
import nextI18NextConfig from "../../next-i18next.config.js"; // in page-level components: ...(await serverSideTranslations( locale, ["common", "login"], nextI18NextConfig )), // in pages/_app.js export default appWithTranslation(AwesoundApp, nextI18NextConfig);
- Get errors like "Error: Error serializing
._nextI18Next.userConfig.use[0].init
returned fromgetStaticProps
;" → AddserializeConfig=false
to next-i18next.config.js and pass config in toappWithTranslation
as per "Unserialisable configs" instructions.
None of thenext-i18next
docs recommend removing theawait
keyword though, so this feels like a losing battle / might introduce some unintended consequences
What I'd like help with:
Can I leave the await
keyword before serverSideTranslations
in the getStaticProps
call, but still have loading
not stuck on true
?
Can I leave my use of next-i18next
as advised by those docs, but modify my nextauth
hooks somehow so loading
isn't stuck on true? Am I just missing some await
keyword elsewhere in my code?
Example code
Compare these two pages:
- /de/test/no-await → translations don't work.
- /de/test/with-await → translations work, but
loading
is stuck ontrue
, I don't know if user is signed in.
To be clear: We want loading
to quickly become false
.
Those two pages have the following code.
next.config.js
(extract):
const webpack = require("webpack");
const { i18n } = require("./next-i18next.config");
const moduleExports = {
i18n, // see require import above
webpack: (config, options) => {
config.resolve.fallback = {
perf_hooks: false, // as per "Quick Solution" on https://dev.to/marcinwosinek/how-to-add-resolve-fallback-to-webpack-5-in-nextjs-10-i6j
fs: false, // 2022-02-02 hack to fix i18n errors, per https://github.com/isaachinman/next-i18next/issues/935#issuecomment-970024608
path: false, // 2022-02-02 hack to fix i18n errors, per https://github.com/isaachinman/next-i18next/issues/935#issuecomment-970024608
};
return config;
},
…
}
next-i18next.config.js
(in its entirety):
const path = require("path");
module.exports = {
i18n: {
defaultLocale: "en",
locales: ["en", "de"],
localePath: path.resolve("./public/locales"),
},
// react: { useSuspense: false },
// useSuspense false does NOT prevent the warning,
// "react-i18next:: You will need to pass in an i18next instance by using initReactI18next" on pages that use getStaticProps and serverSideTranslations
// per https://stackoverflow.com/a/69311205/870121
// We only get this initReactI18next error when we do NOT use await before serverSideTranslations.
debug: true,
};
pages/with-await.jsx
(in its entirety):
See https://awesound-web-iow8f7d1o-awesound.vercel.app/de/test/with-await
// Similar to https://github.com/isaachinman/next-i18next/blob/ee5965183436d9b13d85c9187b3e09983b34ce7f/examples/simple/pages/second-page.js
// This has getStaticProps but not getStaticPaths
// Using 'await' with serverSideTranslations breaks next-auth useSession()
import Layout from "../../components/layout";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import TranslationTestContent from "../../components/test/translationTest";
const usesAwait = true;
const TestPage = ({ mood }) => {
return (
<Layout>
<TranslationTestContent mood={mood} usesAwait={usesAwait} />
</Layout>
);
};
export const getStaticProps = async ({ locale }) => {
const mood = "determined";
return {
props: {
mood,
...(await serverSideTranslations(locale, ["common", "test-page"])),
},
};
};
export default TestPage;
pages/no-await.jsx
(in its entirety):
See https://awesound-web-iow8f7d1o-awesound.vercel.app/de/test/no-await
Note it's the same as pages/with-await.jsx
but usesAwait is false
// Similar to https://github.com/isaachinman/next-i18next/blob/ee5965183436d9b13d85c9187b3e09983b34ce7f/examples/simple/pages/second-page.js
// This has getStaticProps but not getStaticPaths
// Using 'await' with serverSideTranslations breaks next-auth useSession()
import Layout from "../../components/layout";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import TranslationTestContent from "../../components/test/translationTest";
const usesAwait = false;
const TestPage = ({ mood }) => {
return (
<Layout>
<TranslationTestContent mood={mood} usesAwait={usesAwait} />
</Layout>
);
};
export const getStaticProps = async ({ params, locale }) => {
const mood = "determined";
return {
props: {
mood,
...serverSideTranslations(locale, ["common", "test-page"]),
},
};
};
export default TestPage;
Shared component,
import { useTranslation } from "next-i18next";
import Link from "next/link";
import { useSession } from "next-auth/client";
import { useRouter } from "next/router";
import ExclamationTriangleIcon from "../icons/exclamationTriangle";
import CheckCircleIcon from "../icons/checkCircle";
const TranslationTestContent = ({ mood, usesAwait = true }) => {
const [session, loading] = useSession({ required: true });
const { locale } = useRouter();
const { t } = useTranslation("test-page");
return (
<main className="p-12">
<div className="">Test static page with getStaticProps</div>
<div className="">Do a full page refresh if you make any changes.</div>
<h1>This page uses await: {usesAwait ? "yes" : "no"}</h1>
<div>
{usesAwait ? (
<>
Result: translations work, but the `loading` stays true; we don't
know if user is logged in.
</>
) : (
<>
Result: translations don't work, but the `loading` does become
false.
</>
)}
</div>
<h3>Test translation</h3>
<div className="p-4 bg-seco-200 m-4 flex space-x-6">
<div>locale: {locale}</div>
<div>language: {t("language")}</div>
<div>
{t("language") === "language" ? (
<ExclamationTriangleIcon />
) : (
<CheckCircleIcon />
)}
</div>
</div>
<Link href="/">
<button type="button" className="btn btn-prim">
{t("Test page button")}
</button>
</Link>
<h3 className="mt-12">
Test <pre className="inline">`loading`</pre>
</h3>
<div className="p-4 bg-seco-200 m-4">
<div className="flex space-x-2 items-center">
<pre className="inline">loading</pre>:{" "}
<span className="inline">{loading ? "true" : "false"}</span>
{loading ? <ExclamationTriangleIcon /> : <CheckCircleIcon />}
</div>
<div>
<pre className="inline">session</pre> exists?{" "}
<span className="inline">{session ? "true" : "false"}</span>
</div>
</div>
<div className="border p-2 overflow-x-scroll">
session: {JSON.stringify(session)}
</div>
<h3>Look at other props</h3>
<div className="flex space-x-1 gap-x-1">
Via <pre>props</pre>, we have <pre>mood</pre> = <pre>[{mood}]</pre>.
</div>
</main>
);
};
export default TranslationTestContent;