Does useQuery run on server-side rendering? - next.js

I'm new to Nextjs and have some questions about client-side rendering and server-side rendering in Nextjs
I see there are two ways to fetched data on Nextjs. One of them is to use useQuery hook but only callable on React component function. Does it mean that it only running when rendering the page from the client-side
I read a post about how to connect apolloClient to Nextjs. It said that
always create a new instance of apolloClient for SSR and only create one instance of apolloClient for CSR
Here is the example code
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
// If your page has Next.js data fetching methods that use Apollo Client,
// the initial state gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract();
// Restore the cache using the data passed from
// getStaticProps/getServerSideProps combined with the existing cached data
_apolloClient.cache.restore({ ...existingCache, ...initialState });
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === "undefined") return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
Can anyone explain that? I'm sorry if the question are silly

In Next JS:
SSR - Server side rendering - getServerSideProps
SSG - Static site generated - getStaticPaths & getStaticProps
CSR - Client side rendering - everything else
It is important to note that SSG functions are run server-side.
On the client, you only want to create a single global instance of Apollo Client. Creating multiple instances of Apollo Client will make it challenging to keep things in sync with the client. This difficulty is because the Apollo Cache, Apollo Link, etc., will all be stored in different instances of Apollo Client.
In Next, it is common to place the global instance of Apollo Client on the page _app.js and use the Apollo Provider. On the other client-side pages, you'd use the useQuery hook that calls your single global instance.
The server-side (SSR) functions getStaticProps or getServerSideProps do not have access to the client instance of Apollo, client instance of Next, or other server-side functions. Because of this, you must define your Apollo connection on every page that uses getStaticPaths, getStaticProps, or getServerSideProps and needs access to the Apollo client or it will not be available to the server-side calls.
Since the first rule of hooks is that they must only be called at the top level (client-side), you cannot use them in server-side functions. No, you cannot run useQuery in the Next SSR or SSG functions.
The example you provide is keeping the cache in sync and is outdated in how it is defining the client. Here is a simplified example more along the lines of the official docs.
graphqlClient.js
import { ApolloClient, HttpLink, InMemoryCache } from '#apollo/client';
// Used server and client side - can't use react hooks
export const graphqlClient = new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: 'YOUR_GQL_ENDPOINT',
}),
ssrMode: typeof window === 'undefined',
});
_app.js - a single instance that all client pages use because it wraps the whole app
import graphqlClient from 'my/path/graphqlClient';
const App = ({ Component, pageProps }) => {
const client = graphqlClient();
return (
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
);
};
Every page/component that is client-side that can use the useQuery hook because Apollo Client is wrapping the app in _app.js
Client-side query
import { gql, useQuery } from '#apollo/client';
const About = () => {
const { data } = useQuery(YOUR_QUERY); // uses your single instance defined in _app.js
return (
...
)
}
Every page that uses SSR or SSG functions and needs access to Apollo must instantiate a new instance of Apollo.
SSG
import graphqlClient from 'my/path/graphqlClient';
//does not have access to _app.js or client and must define new Apollo Client instance
export const getStaticProps = async () => {
const client = graphqlClient();//
const { data } = await client.query({query: YOUR_QUERY});
};
export const getStaticPaths = async () => {
const client = graphqlClient();
const { data } = await client.query({query: YOUR_QUERY});
};
SSR
import graphqlClient from 'my/path/graphqlClient';
//does not have access to _app.js or client and must define new Apollo Client instance
export const getServerSideProps = async () => {
const client = graphqlClient();
const { data } = await client.query({query: YOUR_QUERY});
};
Lastly, to simplify things you can use graphql-code-generator to auto-generate Apollo query, mutation, etc. hooks (and types for TS users) as well as server-side compatible query and mutation functions for Next.js.

Related

Mixed up data on concurrent requests to vue3 SSR app with apollo client

I'm trying to convert a vue3 SPA that uses an apollo client to SSR with the help of vite-plugin-ssr.
I'm facing an issue of mixed-up data on concurrent requests to the express server. The apollo client instance used to be global in the SPA, so I'm trying to create and use a new instance on every request.
I had this file that included the creation of the client, and wrapper functions around the client functions :
// apollo.ts - SPA
const apolloClient = new ApolloClient({
...
})
export const apolloQuery = async (args) => {
// some logic
const response = await apolloClient.query({...})
// some logic
return response
}
...
Those functions are used in components and store.
The integration example of apollo in vite-plugin-ssr repo uses #vue/apollo-composable
So I tried :
// app.ts
import { DefaultApolloClient } from "#vue/apollo-composable";
import { createApolloClient } from "./path/to/apollo.ts";
const createApp = () => {
const apolloClient = createApolloClient({...})
const app = createSSRApp({
setup() {
provide(DefaultApolloClient, apolloClient)
},
render() {
return h(App, pageProps || {});
}
});
}
// apollo.ts - SSR
import { useApolloClient } from "#vue/apollo-composable"
export const createApolloClient = (args) => {
// some logic
return new ApolloClient({...})
}
export const apolloQuery = async (args) => {
// some logic
const { client } = useApolloClient()
const response = await client.query({...})
// some logic
return response
}
However, this does not work as intended because useApolloClient depends on providing the apollo instance to the app (1), or using the provideApolloClient function (2) :
(1) this means that calls can only be made during component setup or life cycle hooks, as it leverages the Vue core function getCurrentInstance to inject the client. So I can neither use them in the onBeforeRender that vite-plugin-ssr provides, nor in Vue onServerPrefetch hook, nor in the async call to store action.
(2) provideApolloClient sets the client to a global variable, and the same problem occurs on concurrent requests.
Am I at a dead end and a complete refactor of the apollo client use is needed, or am I missing something?

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.

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

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.

Optimizing firebase functions cold start with expressjs

I was trying to figure out how I could optimize cold start times for my firebase functions. After reading this article, I wanted to try it out but I realized that the article specifically targets the base usage of the http onRequest function and doesn't give an example using express.
A similar question popped up here but doesn't seem like there's a clear answer. I saw the author of the article Doug actually commented on the question and he mentions to create a dynamic import for each route in the app since onRequest() only allows for passing the app as its only argument, but I wasn't understanding exactly what he meant by that other than to use the base API without the express app. Ideally I'd be able to use express so I can have finer control over the api url paths and use some of utility that express offers.
Can anyone give me an example of how to use express with Doug's example? Even if I have to define a new express app for each route, I'm okay with that. Just don't see how to configure it that way.
EDIT: To be clear, the goal is to optimize cold starts across all function invocations, not just the http routed ones. From my understanding, Doug's example eliminates the imports being preloaded with single routes declared using onRequest, but it doesn't show how that is possible when defining routes through express.
Assuming each router you split out is defined in it's own file like so:
// $FUNCTIONS_DIR/routes/some-route-handler.js
import express from "express";
const router = express.Router();
/* ... define routes ... */
export default router;
You could then use this middleware to load each route handler module only when it's needed.
function lazyRouterModule(modulePath) {
return async (req, res, next) {
let router;
try {
router = (await import(modulePath)).default;
} catch (err) {
// error loading module, let next() handle it
next(err);
return;
}
router(req, res, next);
}
}
In your sub-function file, you'd use that middleware to create your express app and connect the routes.
// $FUNCTIONS_DIR/fn/my-express.js
import express from "express";
const app = express();
app.use('/api', lazyRouterModule('./routes/api.js'));
app.use('/profiles', lazyRouterModule('./routes/profiles.js'));
export default app;
Then in your main functions file, you'd connect up your subfunction files on-demand:
// $FUNCTIONS_DIR/index.js
import * as functions from 'firebase-functions'
export const myExpress = functions.https
.onRequest(async (request, response) => {
await (await import('./fn/my-express.js')).default(request, response)
});
export const newUserData = functions.firestore.document('/users/{userId}')
.onCreate(async (snap, context) => {
await (await import('./fn/new-user-data.js')).default(snap, context)
});
When lazy-loading modules like this, you will want to lazy-load firebase-admin from a common file so you don't end up calling initializeApp() multiple times.
// $FUNCTIONS_DIR/common/firebase-admin.js
import * as admin from "firebase-admin";
admin.initializeApp();
export = admin;
In any function that wants to use "firebase-admin", you'd import it from here using:
// $FUNCTIONS_DIR/fn/some-function.js OR $FUNCTIONS_DIR/routes/some-route-handler.js
import * as admin from "../common/firebase-admin";
// use admin as normal, it's already initialized

Configure react redux middleware from fetch

I have a Redux middleware that requires some data to be configured via a server call (eg, fetch) which is async / requires promises.
However, every example of createStoresuch as this one seems to use a Singleton pattern for the store. This means that the store gets initialized before my fetch is complete. Because it's middleware, I can't "reconfigure" it after createStore is called.
How can i configure createStore without using a singleton pattern to configure middleware?
How do you fetch those data? If it's just a simple API call. You can easily wait for the data to be returned then pass the data to createStore. Something like this:
const fetchData = () => {
return Promise.resolve({
data: 'Your data here'
});
}
const initialState = window.__INITIAL_STATE__;
fetchData()
.then((data) => {
initialState.somethingYouNeed = data;
const store = createStore(initialState);
// Do the rest here
});

Resources