React Query and using queryClient in both _app.js AND getInitialProps - next.js

So, in the docs, we have this code:
https://react-query.tanstack.com/guides/ssr#using-hydration
RE: The use of 'useRef' to store a reference.
// _app.jsx
import { QueryClient, QueryClientProvider } from 'react-query'
import { Hydrate } from 'react-query/hydration'
export default function MyApp({ Component, pageProps }) {
const queryClientRef = React.useRef()
if (!queryClientRef.current) {
queryClientRef.current = new QueryClient()
}
return (
<QueryClientProvider client={queryClientRef.current}>
<Hydrate state={pageProps.dehydratedState}>
<Component {...pageProps} />
</Hydrate>
</QueryClientProvider>
)
}
BUT, I also need to store some fetch calls IN the "cache" in the MyApp.getInitialProps.... how is that gonna happen IF I create an instance with useRef in function above? Meaning, how is my "getInitialProps" gonna get that instance?
MyApp.getInitialProps = async (appContext) => {
// in here, I do a fetch and get some data I need for SSR
// user Session etc...
const { user } = await fetchUserSession();
// WHAT INSTANCE IS THIS?
queryClient.setQueryData('user', user || {});
return {
...appProps,
dehydratedState: dehydrate(queryClient),
}
}
I am currently defining queryClient = new QueryClient() at the top of the page, so "both" can use it. But I think that is causing some issues with hydration when I npm run build this app.
Remember, this is in "_app.js" so I have to use getInitialProps.
The reason I am doing it here is because we need the users session sitewide, no matter what page they and on. So, rather than do this in every single /page/, just do it in _app.js, so the whole site needs that? The /page/ are Static Generated.

for prefetching on the server, you just create a new QueryClient like described further down on the page you have linked:
export async function getStaticProps() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery('posts', getPosts)
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
}
Here, you create a new empty client, prefetch and take the state and dehydrate it. Then, on the frontend, that state is put into your instance client from MyApp. This is just a way of getting the data from that server-side cache into the client-side cache.

Related

How do I stop auth middleware in NextJS applying to server-side/pre-rendering requests?

I'm trying to require the user to be logged in to access certain routes.
I have added the following middleware, as per the docs, but am having difficulty getting it to work.
I thought the issue was down to the server-side pre-rendered page always being created while unauthenticated, but I'm no longer sure. Middleware should only run on client-side requests from a browser, right?
When I include the block marked with 🟡s, the redirect does not happen.
When it's removed, the redirect always happens, even if the user is logged in.
Note that we're using "next": "^12.3.3", and we're not ready to upgrade to next v13 yet.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import * as auth from 'src/lib/auth';
const pathsNeedingAuth = [
'/dashboard$',
'/account/(password|reset|delete)$',
];
export default async function middleware(request: NextRequest) {
if (typeof window === 'undefined') { // 🟡
return NextResponse.next(); // 🟡
} // 🟡
const pathNeedsAuth = pathsNeedingAuth.some(
(path) => new RegExp(path).test(request.nextUrl.pathname),
);
if (!pathNeedsAuth) {
return NextResponse.next();
}
const isAuthed = await auth.isAuthenticated();
if (isAuthed) {
return NextResponse.next();
}
const url = request.nextUrl.clone();
url.searchParams.set('redirectUrl', url.pathname);
url.pathname = '/account/login';
return NextResponse.redirect(url);
}
export const config = {
matcher: [...pathsNeedingAuth],
};
Any help will be appreciated! I know the structure of the code is a little odd, but that's simply from changing it while trying various things.
Redirects are incompatible with next export.

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)

nextjs-vercel api routes not working in production and development [duplicate]

I'm new to Next.js and I'm trying to understand the suggested structure and dealing with data between pages or components.
For instance, inside my page home.js, I fetch an internal API called /api/user.js which returns some user data from MongoDB. I am doing this by using fetch() to call the API route from within getServerSideProps(), which passes various props to the page after some calculations.
From my understanding, this is good for SEO, since props get fetched/modified server-side and the page gets them ready to render. But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps(). So what am I suppose to do to comply to good practice and good SEO?
The reason I'm not doing the required calculations for home.js in the API route itself is that I need more generic data from this API route, as I will use it in other pages as well.
I also have to consider caching, which client-side is very straightforward using SWR to fetch an internal API, but server-side I'm not yet sure how to achieve it.
home.js:
export default function Page({ prop1, prop2, prop3 }) {
// render etc.
}
export async function getServerSideProps(context) {
const session = await getSession(context)
let data = null
var aArray = [], bArray = [], cArray = []
const { db } = await connectToDatabase()
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
if (session) {
const hostname = process.env.NEXT_PUBLIC_SITE_URL
const options = { headers: { cookie: context.req.headers.cookie } }
const res = await fetch(`${hostname}/api/user`, options)
const json = await res.json()
if (json.data) { data = json.data }
// do some math with data ...
// connect to MongoDB and do some comparisons, etc.
But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps().
You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps. Note that this only applies to calls to internal API routes - it's perfectly fine to call external APIs from getServerSideProps.
From Next.js getServerSideProps documentation:
It can be tempting to reach for an API Route when you want to fetch
data from the server, then call that API route from
getServerSideProps. This is an unnecessary and inefficient approach,
as it will cause an extra request to be made due to both
getServerSideProps and API Routes running on the server.
(...) Instead, directly import the logic used inside your API Route
into getServerSideProps. This could mean calling a CMS, database, or
other API directly from inside getServerSideProps.
(Note that the same applies when using getStaticProps/getStaticPaths methods)
Here's a small refactor example that allows you to have logic from an API route reused in getServerSideProps.
Let's assume you have this simple API route.
// pages/api/user
export default async function handler(req, res) {
// Using a fetch here but could be any async operation to an external source
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
res.status(200).json(jsonData)
}
You can extract the fetching logic to a separate function (can still keep it in api/user if you want), which is still usable in the API route.
// pages/api/user
export async function getData() {
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
return jsonData
}
export default async function handler(req, res) {
const jsonData = await getData()
res.status(200).json(jsonData)
}
But also allows you to re-use the getData function in getServerSideProps.
// pages/home
import { getData } from './api/user'
//...
export async function getServerSideProps(context) {
const jsonData = await getData()
//...
}
You want to use the logic that's in your API route directly in
getServerSideProps, rather than calling your internal API. That's
because getServerSideProps runs on the server just like the API routes
(making a request from the server to the server itself would be
pointless). You can read from the filesystem or access a database
directly from getServerSideProps
As I admit, what you say is correct but problem still exist. Assume you have your backend written and your api's are secured so fetching out logic from a secured and written backend seems to be annoying and wasting time and energy. Another disadvantage is that by fetching out logic from backend you must rewrite your own code to handle errors and authenticate user's and validate user request's that exist in your written backend. I wonder if it's possible to call api's within nextjs without fetching out logic from middlewars? The answer is positive here is my solution:
npm i node-mocks-http
import httpMocks from "node-mocks-http";
import newsController from "./api/news/newsController";
import logger from "../middlewares/logger";
import dbConnectMid from "../middlewares/dbconnect";
import NewsCard from "../components/newsCard";
export default function Home({ news }) {
return (
<section>
<h2>Latest News</h2>
<NewsCard news={news} />
</section>
);
}
export async function getServerSideProps() {
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
async function callMids(req, res, index, ...mids) {
index = index || 0;
if (index <= mids.length - 1)
await mids[index](req, res, () => callMids(req, res, ++index, ...mids));
}
await callMids(
req,
res,
null,
dbConnectMid,
logger,
newsController.sendAllNews
);
return {
props: { news: res._getJSONData() },
};
}
important NOTE: don't forget to use await next() instead of next() if you use my code in all of your middlewares or else you get an error.
Another solution: next connect has run method that do something like mycode but personally I had some problems with it; here is its link:
next connet run method to call next api's in serverSideProps
Just try to use useSWR, example below
import useSWR from 'swr'
import React from 'react';
//important to return only result, not Promise
const fetcher = (url) => fetch(url).then((res) => res.json());
const Categories = () => {
//getting data and error
const { data, error } = useSWR('/api/category/getCategories', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
if (data){
// {data} is completed, it's ok!
//your code here to make something with {data}
return (
<div>
//something here, example {data.name}
</div>
)
}
}
export default Categories
Please notice, fetch only supports absolute URLs, it's why I don't like to use it.
P.S. According to the docs, you can even use useSWR with SSR.

getServerSideProps does not render dynamic pages in production, and show 404 error

I'm working with next.js, in development mode everything works fine, but in production mode I have a problem when rendering the pages dynamically.
I have the following path inside the pages folder
pages/user/[id], and this component is where I call the getServerSideProps function.
import headers from '../../headers';
export async function getServerSideProps(context) {
const URL = 'https://somewhere...';
let { id } = context.params;
const apiResponse = await fetch(
`${URL}/${id}/detail`,
{
headers: headers,
}
);
if (apiResponse.ok) {
const data = await apiResponse.json();
return {
props: data, // will be passed to the page component as props
};
} else {
return { props: {} };
}
}
My problem is the following, I need to send in headers the authentication token that I only get when I login and I get the 2FA code, so in build time, that info does not exist and I get a 401 error no authorizate when execute npm run build and when I access to /user/34 for example I get a 404 error.
I have checked these questions at stackoverflow:
NextJs: Static export with dynamic routes
https://stackoverflow.com/questions/61724368/what-is-the-difference-between-next-export-and-next-build-in-next-js#:~:text=After%20building%2C%20next%20start%20starts,can%20serve%20with%20any%20host.&text=js%20will%20hydrate%20your%20application,to%20give%20it%20full%20interactivity.
next.js getStaticPaths list every path or only those in the immediate vicinity?
I have some parts in my app that are statics and works fine, but the problem is with the dynamic paths, as next.js is not creating those paths.
EDIT: I'll include a image with other problem, if after the fetch in the if I just say :
if(apiResponse){ //without 'ok'
}
I'll recieve this errror:
return {
props: data, // will be passed to the page component as props
}
props should be object
return {
props: {data} // or props: {data:data}
}

Apollo Client and Redux setup causes infinite render loop

I'm trying to link up React Apollo with Redux so Apollo performs the queries and mutations, and the returned data is dispatched to the Redux store in order to distribute the data around the app.
I believe I'm close to getting it right, but for some reason the app goes into an infinite loop of Redux dispatches, and I can't figure out why.
See code below:
class Admin extends Component {
constructor(props) {
super(props);
}
render({
adminAllTokens
}, {}) {
return ( /* JSX */ )
);
}
}
const AllRefreshTokens = gql `
query {
allUsers {
refreshToken
email
}
}
`;
const gqlWrapper = graphql(AllRefreshTokens, {
props: ({
ownProps,
data
}) => {
ownProps.receivedAdminTokens(data.allUsers); //dispatch to Redux store
return {
...data,
gqladminAllTokens
};
}
});
function mapStateToProps(state, ownProps) {
return {
adminAllTokens: state.auth.adminAllTokens
};
}
function mapDispatchToProps(dispatch) {
return {
receivedAdminTokens: tokens => {
dispatch(adminTokensReceived(tokens));
}
};
}
const reduxWrapper = connect(mapStateToProps, mapDispatchToProps);
export default compose(reduxWrapper, gqlWrapper)(Admin);
The adminTokensReceived() action is in the reducer file:
export const adminTokensReceived = tokens => ({
type: 'ADMIN_TOKENS_RECEIVED',
tokens
});
The GraphQL query only sends one network request, but the console is showing the ADMIN_TOKENS_RECEIVED action dispatching constantly and crashes the browser.
Thanks in advance
Whenever the Apollo HOC receives new props, it causes your action to fire, which updates the store and sends new props to your Apollo HOC, which causes your action to fire...
There's a couple of different ways you could handle this. In my mind, the most straightforward would be to drop the graphql HOC and use withApollo instead. Something like:
compose(
withApollo,
connect(mapStateToProps, mapDispatchToProps)
lifecycle({
componentDidMount() {
const { client } = this.props
client.query({ query: AllRefreshTokens })
.then(({data}) => {
receivedAdminTokens(data.allUsers)
})
.catch( //any error handling logic )
}
})
)
The above uses recompose's lifecycle but you could just as easily stick the componentDidMount method inside your component.
That said, it seems a little redundant to use Redux to store the results of your GraphQL queries when Apollo already does it for you.
Apollo's default behavior is to retrieve the data from the cache first, and only make a network request if the data doesn't exist (which is also why you only saw the one network call). That means any number of components inside your app could be wrapped with the same graphql HOC, and only the first component to be rendered would trigger a request to your GraphQL endpoint -- all other components would get their data from the cache.

Resources