I have global styles defined in my nextjs application inside /styles/globals.css and imported in _app.tsx
// import default style
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default MyApp;
and page in in pages directory (pages/index.tsx)
export async function getServerSideProps({req, res }) {
// Some fetch here
return {
props: {
someProps: objectFromFetchThere,
},
};
}
const Home: NextPage<{ someProps: SomeProps[] }> = ({ someProps }) => {
return (
<>
<Head>
<title>Home page</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Layout>
// More code here
</Layout>
</>
);
};
When I'm running an application with next dev then everything is working fine (styles are loaded, getServerSideProps is called etc.), but when I'm running in production (next build && NODE_ENV=production ts-node src/server.ts) then global styles are not loaded (and _app file is also not used). Does it mean that I can't use global styles in pages with getServerSideProps exported? I didn't find anything related to that in NextJS documentation. Am I missing something here?
My custom server:
(async () => {
try {
const expressServer = express();
await app.prepare();
// Some custom routes defined over there like /facility - nothing that can overlap with styles.
// Error middleware has to be used after other custom routes
expressServer.use(
(err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
res.status(500).send("Unexpected error occurred.");
}
);
expressServer.all("*", async (req: Request, res: Response) => {
try {
await handle(req, res);
} catch (e) {
console.error("Error occurred handling", req.url, e);
res.statusCode = 500;
res.end("internal server error");
}
});
expressServer.listen(port, (err?: any) => {
if (err) throw err;
console.log(
`> Ready on localhost:${port} - env ${process.env.NODE_ENV}`
);
});
} catch (e) {
console.error(e);
process.exit(1);
}
})();
This issue is related to the custom server and typescript used at the same time. Changing "target": "es5" to "target": "es6" fixed the issue. A solution is taken from https://stackoverflow.com/a/67520930/7932025.
Related
I'm working with NextJS, Next-auth and Django as backend. I'm using the credentials provider to authenticate users. Users are authenticated against the Django backend and the user info together with the accesstoken is stored in the session.
I'm trying to use useSWR now to fetch data from the backend. (no preloading for this page required, that's why I'm working with SWR) I need to send the access_token from the session in the fetcher method from useSWR. However I don't know how to use useSWR after the session is authenticated. Maybe I need another approach here.
I tried to wait for the session to be authenticated and then afterwards send the request with useSWR, but I get this error: **Error: Rendered more hooks than during the previous render.
**
Could anybody help with a better approach to handle this? What I basically need is to make sure an accesstoken, which I received from a custom backend is included in every request in the Authorization Header. I tried to find something in the documentation of NextJS, Next-Auth or SWR, but I only found ways to store a custom access_token in the session, but not how to include it in the Header of following backend requests.
This is the code of the component:
import { useSession } from "next-auth/react";
import useSWR from 'swr';
import axios from 'axios'
export default function Profile() {
const { data: session, status } = useSession();
// if session is authenticated then fetch data
if (status == "authenticated") {
// create config with access_token for fetcher method
const config = {
headers: { Authorization: `Bearer ${session.access_token}` }
};
const url = "http://mybackend.com/user/"
const fetcher = url => axios.get(url, config).then(res => res.data)
const { data, error } = useSWR(url, fetcher)
}
if (status == "loading") {
return (
<>
<span>Loading...</span>
</>
)
} else {
return (
<>
{data.email}
</>
)
}
}
you don't need to check status every time. what you need to do is to add this function to your app.js file
function Auth({ children }) {
const router = useRouter();
const { status } = useSession({
required: true,
onUnauthenticated() {
router.push("/sign-in");
},
});
if (status === "loading") {
return (
<div> Loading... </div>
);
}
return children;
}
then add auth proprety to every page that requires a session
Page.auth = {};
finally update your const App like this
<SessionProvider session={pageProps.session}>
<Layout>
{Component.auth ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
)}
</Layout>
</SessionProvider>
so every page that has .auth will be wrapped with the auth component and this will do the work for it
now get rid of all those if statments checking if session is defined since you page will be rendered only if the session is here
Thanks to #Ahmed Sbai I was able to make it work. The component now looks like this:
import { useSession } from "next-auth/react";
import axios from "axios";
import useSWR from 'swr';
Profile.auth = {}
export default function Profile() {
const { data: session, status } = useSession();
// create config with access_token for fetcher method
const config = {
headers: { Authorization: `Bearer ${session.access_token}` }
};
const url = "http://mybackend.com/user/"
const fetcher = url => axios.get(url, config).then(res => res.data)
const { data, error } = useSWR(url, fetcher)
if (data) {
return (
<>
<span>{data.email}</span>
</>
)
} else {
return (
<>
Loading...
</>
)
}
}
App component and function:
function Auth({ children }) {
const router = useRouter();
const { status } = useSession({
required: true,
onUnauthenticated() {
router.push("/api/auth/signin");
},
});
if (status === "loading") {
return (
<div> Loading... </div>
);
}
return children;
}
function MyApp({
Component,
pageProps: { session, ...pageProps },
}) {
return (
<SessionProvider session={pageProps.session}>
{Component.auth ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
)}
</SessionProvider>
)
}
When I test getServerSideProps() in development mode, prop data in my landing page is updated constantly, because the app is under fast build mode.
But when the app is deployed in vercel, the data in landing page is not updated even though my database (mongoDB) has been updated.
If I check Heroku logs (where backend is deployed), there is no POST request by client when I (user) visit landing page second time. Therefore, I am assuming that my browser uses the cached html page and not sending request to server.
Could anyone help me to explain what is the issue?
import { ApolloClient, InMemoryCache } from "#apollo/client";
import { GetServerSideProps } from "next";
import { GetAllPosts as query } from "#network/queries";
const client = new ApolloClient({
uri: //my backend uri,
cache: new InMemoryCache(),
});
//pages/_app.ts
import type { AppProps } from "next/app";
function MyApp({ Component, pageProps }: AppProps) {
return (
<ApolloProvider client={client}>
<AuthContext.Provider value={authService}>
<Component {...pageProps} />
</AuthContext.Provider>
</ApolloProvider>
);
}
//pages/index.tsx
export const getServerSideProps: GetServerSideProps = async () => {
let posts = [];
try {
const {
data: { getAllPosts },
} = await client.query({ query });
posts = !!getAllPosts.length && getAllPosts;
} catch (err) {
console.error(`----------error --------- ${err}`);
} finally {
return {
props: {
posts,
},
};
}
};
export default function Landing({ posts }: Props) {
////// react code
}
For example, I have a dynamic route /blog/[article-id].
When visiting an existing blog post /blog/id-that-exist, it works as expected, and now I want to handle the case /blog/id-that-does-not-exist properly.
The code in /blog/[id].jsx looks something like:
export const getStaticPaths async () => {
return {
fallback: true,
paths: (await sequelize.models.Article.findAll()).map(
article => {
return {
params: {
pid: article.slug,
}
}
}
),
}
}
export const getStaticProps async () => {
// Try to get it from the database. Returns none if does not exist.
const article = await sequelize.models.Article.findOne({
where: { slug: pid },
});
return { props: { article: article } };
}
const ArticlePage = (props) => {
// This can happen due to fallback: true while waiting for
// a page that was not rendered at build time to build.
const router = useRouter()
if (router.isFallback) {
return <div>loading</div>;
}
return (
<div>{props.article.body}</div>
);
};
export const getStaticPaths = getStaticPathsArticle;
export const getStaticProps = getStaticPropsArticle;
export default ArticlePage;
I saw this related question: How to handle not found 404 for dynamic routes in Next.js which is calling API? but I'm not sure if it's the same as I'm asking here, as this does not depend on any external API being used.
notFound: true from Next.js 10
Starting in Next.js 10, we can do:
export const getStaticProps async () => {
// Try to get it from the database. Returns none if does not exist.
const article = await sequelize.models.Article.findOne({
where: { slug: pid },
});
if (!article) {
return {
notFound: true
}
}
return { props: { article: article } };
}
as documented at: https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation
When notFound is returned, the rendering function ArticlePage just never gets called, and the default 404 page is returned instead.
Note however that ArticlePage did get
For some reason in development mode:
I don't get the expected 404 HTTP status code
ArticlePage, so if you forgot to handle the fallback case, the it might crash due to missing properties
which was confusing me a bit. But in production mode, everything works as expected.
Workaround before Next.js 10
As shown https://github.com/vercel/next.js/discussions/10960#discussioncomment-1201 you could previously do something like:
const ArticlePage = (props) => {
if (!props.article) {
return <>
<Head>
<meta name="robots" content="noindex">
</Head>
<DefaultErrorPage statusCode={404} />
</>
}
return (
<div>{props.article.body}</div>
);
};
but this is not ideal because it does not set the HTTP return code correctly I believe, and I don't know how to do it.
Tested on Next.js 10.2.2.
I've read your answer regarding the solution after Next.js v.10, but I didn't get what was the problem in showing the expected http 404 code during development.
I use Next.JS v.12 and I get the expected 404 normally in development
import { GetStaticPaths, GetStaticProps } from 'next'
import { useRouter } from 'next/router'
import { ParsedUrlQuery } from 'querystring'
import Loading from '../../components/loading'
export const getStaticPaths: GetStaticPaths = async () => {
//your paths
return { paths, fallback: true }
}
export const getStaticProps: GetStaticProps = async ({ params }: { params?: ParsedUrlQuery }) => {
//get your props
if (!target){
return {notFound: true}
}
return { props: { ... }, revalidate: 86400}
}
function Index({ ... }) {
const router = useRouter()
if (router.isFallback) {
return <Loading />
}
return (
<div>
//my content
</div>
)
}
export default Index
When the target isn't found, it renders my custom 404 component in pages/404.tsx if I created one or just the default 404 page.
This should work normally during development and production.
This page is the most relevant information I can find but it isn't enough.
I have a generic component that displays an appbar for my site. This appbar displays a user avatar that comes from a separate API which I store in the users session. My problem is that anytime I change pages through next/link the avatar disappears unless I implement getServerSideProps on every single page of my application to access the session which seems wasteful.
I have found that I can implement getInitialProps in _app.js like so to gather information
MyApp.getInitialProps = async ({ Component, ctx }) => {
await applySession(ctx.req, ctx.res);
if(!ctx.req.session.hasOwnProperty('user')) {
return {
user: {
avatar: null,
username: null
}
}
}
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return {
user: {
avatar: `https://cdn.discordapp.com/avatars/${ctx.req.session.user.id}/${ctx.req.session.user.avatar}`,
username: ctx.req.session.user.username
},
pageProps
}
}
I think what's happening is this is being called client side on page changes where the session of course doesn't exist which results in nothing being sent to props and the avatar not being displayed. I thought that maybe I could solve this with local storage if I can differentiate when this is being called on the server or client side but I want to know if there are more elegant solutions.
I managed to solve this by creating a state in my _app.js and then setting the state in a useEffect like this
function MyApp({ Component, pageProps, user }) {
const [userInfo, setUserInfo] = React.useState({});
React.useEffect(() => {
if(user.avatar) {
setUserInfo(user);
}
});
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<NavDrawer user={userInfo} />
<Component {...pageProps} />
</ThemeProvider>
);
}
Now the user variable is only set once and it's sent to my NavDrawer bar on page changes as well.
My solution for this using getServerSideProps() in _app.tsx:
// _app.tsx:
export type AppContextType = {
navigation: NavigationParentCollection
}
export const AppContext = createContext<AppContextType>(null)
function App({ Component, pageProps, navigation }) {
const appData = { navigation }
return (
<>
<AppContext.Provider value={appData}>
<Layout>
<Component {...pageProps} />
</Layout>
</AppContext.Provider>
</>
)
}
App.getInitialProps = async function () {
// Fetch the data and pass it into the App
return {
navigation: await getNavigation()
}
}
export default App
Then anywhere inside the app:
const { navigation } = useContext(AppContext)
To learn more about useContext check out the React docs here.
I try to fetch data (slugs of categories) from my server and use it inside the header navbar that is reusable in all the site.
In _app I have the layout which contains the header component:
function MyApp({ Component, appProps, pageProps, router }) {
return (
<>
<SlugContext.Provider value={{ slugs: pageProps.response }}>
<Layout>
<Component {...pageProps} />
</Layout>
</SlugContext.Provider>
</>
);
}
MyApp.getInitialProps = async ctx => {
const response = await axios
.get(`${API}/categories/`)
.then(res => {
return res.data;
})
.catch(error => {
if (error) {
console.log(error);
}
});
const pageProps = await App.getInitialProps({ response, ...ctx });
return { pageProps: { ...pageProps, response } };
};
In the header navbar I put the context:
const context = useContext(SlugContext);
and inside I have links like:
<NavLink href={`/categories/${context.slugs[0].slug}`} onClick={closeMobileMenu}>
<NavLink href={`/categories/${context.slugs[1].slug}`} onClick={closeMobileMenu}>
THE PROBLEM: When I console log inside the header I can see my slugs. When I click on one link -> it directs me to the slug, like: http://localhost:3000/categories/slugone
Bu the following message appears:
Unhandled Runtime Error
TypeError: Cannot read property '0' of undefined
...........
href={`/categories/${context.slugs[0].slug}`}===undefined
Why is it undefined with 0, if before he have all the slugs data? It has to be in the context, why does it disappear and how can I handle it?