props passed to Page from getServerSideProps is always undefined - next.js

I'm using nextjs 9.3.5 and even the simplest example of getServerSideProps is always failing:
function Page({ data })
{
//Prints undefined
console.log(data);
return <div>Data in props: {data}</div>
}
export async function getServerSideProps()
{
var data = "Hello";
//Prints "Hello"
console.log(data);
return { props: { data } };
}
export default Page
This is basically a cut and paste from the very simple example on the nextjs website. getInitialProps works fine.

In case you added _app.js file into your project according official documentation you need add Component and pageProps inside, here is a minimal implementation of _app.js file.
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp

I had the same problem and I realised that I was using getServerSideProps in the component that is not the actual page, rather it was my card component that I was displaying on the page.

This should be in the structure of your json that you are returning as a prop.
Try this:
export async function getServerSideProps() {
const data = {title:"Hello Sir"};
return { props: data }
}

Related

Next.js access environment variable in _app.js file

I am building a blog using Storyblok (v2) and Next.js (v13). I understand that NEXT_PUBLIC environment variables can be exposed to the browser, and I do not want my access token exposed. I have used the api folder in the past to accomplish this in component files, but I do not think that I can use that for the page files—unless I am wrong. I have read the documentation from Next.js, and I am confused as to how I can accomplish this in my _app.js file. I am still learning about how to properly hide these using Next.js, and I was successful in my previous attempt.
This is what I have tried so far after searching for answers. I have added my access token to a .env file in the root of my project. I did not use NEXT_PUBLIC. I left that off because in my other project where I was able to hide my environment variables from the browser, that is how I did it. I also added it to my next.config.js file based on the information that I found in my search. This is what that file looks like:
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
env: { ACCESS_TOKEN: process.env.ACCESS_TOKEN },
}
module.exports = nextConfig
My _app.js file looks like this:
import { storyblokInit, apiPlugin } from '#storyblok/react'
import '../styles/globals.css'
storyblokInit({
accessToken: process.env.ACCESS_TOKEN,
use: [apiPlugin],
});
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
Of course the above does not work. When I add {process.env.ACCESS_TOKEN} to any page, it is showing the token. I understand why this does not work based on the docs that I read, but I just wanted to try everything that I could before posting my question here.
Prior to doing these things, I also tried adding my variable to the .env file in the root of my project (not using the NEXT_PUBLIC prefix), and then in my _app.js file, I had the following:
import { storyblokInit, apiPlugin } from '#storyblok/react'
import '../styles/globals.css'
storyblokInit({
accessToken: token,
use: [apiPlugin],
});
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
function getStaticProps() {
return {
props: {
token: props.env.ACCESS_TOKEN
}
}
}
My next.config.js file wasn't touched, so it did not contain env in it. The error that I get is that token is not defined. This article is where I found the steps that I attempted.
I started using trial and error after that—mainly because I went down the Google rabbit hole and got super frustrated. This was my Hail Mary:
import { storyblokInit, apiPlugin } from '#storyblok/react'
import '../styles/globals.css'
function MyApp({ Component, pageProps, props }) {
storyblokInit({
accessToken: props.token,
use: [apiPlugin],
});
return <Component {...pageProps} />
}
export default MyApp
function getStaticProps() {
return {
props: {
token: props.env.ACCESS_TOKEN
}
}
}
The error that I get here is: Cannot read properties of undefined (reading 'token')
Is what I am trying to do even possible in the _app.js file? I am happy to read any other documentation that I may not have found in my search if that is a better response to my question.

Not-found page does not work in next.js 13

This is the sturcure of my next.js project.
And my 404.js page is :
'use client';
export default function NotFound() {
return (
<div>
<h2>Not Found</h2>
</div>
);
}
when I enter the wrong route it does not work and does not go to my custom page and goes to next.js 404 page.
why, Where am I wrong?
thanks in advance.
NextJS13 doesnt do error handling in this format, you dont want to use a file named 404.js but instead a file named error.js.
This will catch any errors sent from an API request returning a 404 response.
Docs here: https://beta.nextjs.org/docs/routing/error-handling
If your API instead returns a 200 response but an empty body, you could create another component named not-found.js, import that into the file you want it to show on, and return it on if the api is empty, for example:
app/dashboard/not-found.js
export default function NotFound() {
return (
<>
<h2>Not Found</h2>
<p>Could not find requested resource</p>
</>
);
}
app/dashboard/index.js:
import { notFound } from 'next/navigation';
async function fetchUsers(id) {
const res = await fetch('https://...');
if (!res.ok) return undefined;
return res.json();
}
export default async function Profile({ params }) {
const user = await fetchUser(params.id);
if (!user) {
notFound();
}
// ...
}
Docs here: https://beta.nextjs.org/docs/api-reference/notfound
To create a not-found page in Next.js using the app folder, you can follow these steps:
Create a new folder named pages in your project's root directory.
In the pages folder, create a new file named 404.js.
In the 404.js file, add the following code to render the Not Found page:
const NotFound = () => {
return (
<div>
<h1>404 - Not Found</h1>
</div>
)
}
export default NotFound
In your _app.js file, add a catch-all route to display the Not Found page for any unknown routes:
import App, { Container } from 'next/app'
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps }
}
render() {
const { Component, pageProps } = this.props
return (
<Container>
<Component {...pageProps} />
</Container>
)
}
}
export default MyApp
Now, when a user visits a route that does not exist in your application, the Not Found page will be displayed.

How to access query params in Next.js SSG, ISR [duplicate]

I want to get query string from URL on Next.js static site generation.
I found a solution on SSR but I need one for SSG.
Thanks
import { useRouter } from "next/router";
import { useEffect } from "react";
const router = useRouter();
useEffect(() => {
if(!router.isReady) return;
const query = router.query;
}, [router.isReady, router.query]);
It works.
I actually found a way of doing this
const router = useRouter()
useEffect(() => {
const params = router.query
console.log(params)
}, [router.query])
As other answers mentioned, since SSG doesn't happen at request time, you wouldn't have access to the query string or cookies in the context, but there's a solution I wrote a short article about it here https://dev.to/teleaziz/using-query-params-and-cookies-in-nextjs-static-pages-kbb
TLDR;
Use a middleware that encodes the query string as part of the path,
// middleware.js file
import { NextResponse } from 'next/server'
import { encodeOptions } from '../utils';
export default function middleware(request) {
if (request.nextUrl.pathname === '/my-page') {
const searchParams = request.nextUrl.searchParams
const path = encodeOptions({
// you can pass values from cookies, headers, geo location, and query string
returnVisitor: Boolean(request.cookies.get('visitor')),
country: request.geo?.country,
page: searchParams.get('page'),
})
return NextResponse.rewrite(new URL(`/my-page/${path}`, request.nextUrl))
}
return NextResponse.next()
}
Then make your static page a folder that accepts a [path]
// /pages/my-page/[path].jsx file
import { decodeOptions } from '../../utils'
export async function getStaticProps({
params,
}) {
const options = decodeOptions(params.path)
return {
props: {
options,
}
}
}
export function getStaticPaths() {
return {
paths: [],
fallback: true
}
}
export default function MyPath({ options }) {
return <MyPage
isReturnVisitor={options.returnVisitor}
country={options.country} />
}
And your encoding/decoding functions can be a simple JSON.strinfigy
// utils.js
// https://github.com/epoberezkin/fast-json-stable-stringify
import stringify from 'fast-json-stable-stringify'
export function encodeOptions(options) {
const json = stringify(options)
return encodeURI(json);
}
export function decodeOptions(path) {
return JSON.parse(decodeURI(path));
}
You don't have access to query params in getStaticProps since that's only run at build-time on the server.
However, you can use router.query in your page component to retrieve query params passed in the URL on the client-side.
// pages/shop.js
import { useRouter } from 'next/router'
const ShopPage = () => {
const router = useRouter()
console.log(router.query) // returns query params object
return (
<div>Shop Page</div>
)
}
export default ShopPage
If a page does not have data fetching methods, router.query will be an empty object on the page's first load, when the page gets pre-generated on the server.
From the next/router documentation:
query: Object - The query string parsed to an object. It will be
an empty object during prerendering if the page doesn't have data
fetching
requirements.
Defaults to {}
As #zg10 mentioned in his answer, you can solve this by using the router.isReady property in a useEffect's dependencies array.
From the next/router object documentation:
isReady: boolean - Whether the router fields are updated
client-side and ready for use. Should only be used inside of
useEffect methods and not for conditionally rendering on the server.
you don't have access to the query string (?a=b) for SSG (which is static content - always the same - executed only on build time).
But if you have to use query string variables then you can:
still statically pre-render content on build time (SSG) or on the fly (ISR) and handle this route by rewrite (next.config.js or middleware)
use SSR
use CSR (can also use SWR)

Using the context API in Next.js

I'm building a simple Next.js website that consumes the spacex graphql API, using apollo as a client. I'm trying to make an api call, save the returned data to state and then set that state as context.
Before I save the data to state however, I wanted to check that my context provider was actually providing context to the app, so I simply passed the string 'test' as context.
However, up[on trying to extract this context in antoher component, I got the following error:
Error: The default export is not a React Component in page: "/"
My project is set up as follows, and I'm thinking I may have put the context file in the wrong place:
pages
-api
-items
-_app.js
-index.js
public
styles
next.config.js
spacexContext.js
Here's the rest of my app:
spaceContext.js
import { useState,useEffect,createContext } from 'react'
import { ApolloClient, InMemoryCache, gql } from "#apollo/client"
export const LaunchContext = createContext()
export const getStaticProps = async () => {
const client = new ApolloClient({
uri: 'https://api.spacex.land/graphql/',
cache: new InMemoryCache()
})
const { data } = await client.query({
query: gql`
query GetLaunches {
launchesPast(limit: 10) {
id
mission_name
launch_date_local
launch_site {
site_name_long
}
links {
article_link
video_link
mission_patch
}
rocket {
rocket_name
}
}
}
`
});
return {
props: {
launches: data.launchesPast
}
}
}
const LaunchContextProvider = (props) => {
return(
<LaunchContext.Provider value = 'test'>
{props.children}
</LaunchContext.Provider>
)
}
export default LaunchContextProvider
_app.js
import LaunchContextProvider from '../spacexContext'
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
return (
<LaunchContextProvider>
<Component {...pageProps} />
</LaunchContextProvider>
)
}
export default MyApp
Any suggestions on why this error is appearing and how to fix it?

Next JS fetch data once to display on all pages

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.

Resources