Algolia and Next.js - Index Updates and getStaticProps Side Effects - reactjs

I've got a fairly complex Next.js site that is mostly statically rendered and I'm adding Algolia search into the site. The mechanics of Algolia require that you use their API and notify them of additional content every time it gets published.
My question is where in the Next app you notify Algolia. Given that my site is using getStaticProps to fetch data, I figured this is the logical place to notify and update Algolia. It works but wondering what others have done, best practice, tradeoffs, etc.
The lifecycle looks like this:
Get data from database via GraphQL (using headless CMS Prismic)
Normalize data inside Next before sending to Algolia
Send data to Algolia (their system reconciles old/new records be referencing uuid)
Code that makes this happen:
// Use nextjs getStaticProps, destructure default params
export async function getStaticProps({ params, preview = false, previewData }) {
// Data fetch (assume getAllNewsForLandingPage gets an array of obj)
pageData.data = await getAllNewsForLandingPage(params.uid, previewData)
// Format and normalize results
const algoliaFormattedData = dataFormatter(pageData.data)
// Send data back to Algolia, who will reconcile old and new data automatically
await AlgoliaIndex.saveObjects(algoliaFormattedData)
}

Related

Next.js: How to cache initial data on the server (same for all users)

I'm learning Next.js and started building one of my first applications - https://www.codart.io/
If you visit the site, you'll notice a delay until the collections become available: The application makes an initial graphql request to retrieve a json file with a number of NFT collections, which takes a few seconds. This data needs to be available across different components, so I built a global context to store the data (I'm not sure this is the best approach):
https://github.com/bartomolina/codart/blob/main/app/components/collections-context.tsx
const ArtBlocksContext = createContext({
aBCollections: [] as IABCollection[],
cACollections: [] as ICACollection[],
fetchCACollections: () => {},
});
export const useArtBlocks = () => useContext(ArtBlocksContext);
export const ArtblocksProvider = ({ children }: React.PropsWithChildren) => {
...
useEffect(() => {
fetchCACollections();
execute(ArtblocksCollectionsDocument, {}).then((result) => {
I wonder what would be the best way to cache this data (as the data doesn't change often, it could be cached at build time, or ideally, indicating an expiration date i.e. 24 hours). Note that the cache will be shared between all users.
I've been looking at some similar posts, and I got some ideas, although it seems there isn't a clear and simple way to do this:
Using Redux - Seems an overkill for a small project like this.
Use some custom caching libraries - I'd rather not use any external libraries.
Use getStaticProps along with the Context API - It seems you can't getStaticProps within the _app.ts page, so you would need to call it in every page where the context is used?
Use SWR and an API call - SWR will cache the data, but on a per-user basis? i.e. the first time a user visits the site, it will still take a few seconds to load.

Minimizing API calls using GraphQL and React

I’ve written an API in GraphQL (Apollo Server), and a separate application in React (Utilizing Apollo Client). The app itself is really straight forward and doesn't require many calls to the API at all, and because of GraphQL, I can get all of the data a user needs in 2 calls and the only time I need to refetch the data is after a mutation.
I have a signin and a signup mutation, then two more mutations for creating an updating the main object a user interacts with.
I'm fairly familiar with React and GraphQL, but I feel like there has to be a way to make one 'larger' call after the signin/signup mutation that fetches all the data user a needs, rather than making a call to each level of nesting based on the loading of a particular component.
Mutations
SignUp
SignIn
CreateShirt
UpdateShirt
Queries
GetShirts
GetDesigns
Ideally, I could utilize a query similar to
query GetUser {
user {
id
email
shirts {
id
style
design {
name
swatch
}
}
}
}
So.. I could return all of this information in the SignIn / SignUp mutations but then after the UpdateShirt mutation, I don't have a (named) query that I can force to refetch. So then I leaned towards just creating a GetUser query that I could refetch but I don't know where to call it from.. it isn't specific to a component necessarily, more to a status of authentication.
What is the most efficient way to query my API for this information? Ignoring the ability to make nested queries and make the components ask for it seems silly.
I think, this is what you are lookin for?
https://www.apollographql.com/docs/react/data/mutations/#refetching-queries
After calling the mutation, this way, you can re-fetch your queries.
// Refetches two queries after mutation completes
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO, {
refetchQueries: [
{query: GET_POST}, // DocumentNode object parsed with gql
'GetComments' // Query name
],
});
However, if you want one request then that means what you are lookin for is Batching.
You can batch multiple queries, something like:
const { data } = useQuery(gql`
query ShameelQuery {
query1
query2
}
`)
You can find more details in their official docs:
https://www.apollographql.com/blog/apollo-client/performance/batching-client-graphql-queries/

Storing E-Commerce Product Data On LocalStorage

I am busy working on a "Headless" E-Commmerce Application in ReactJS and I Have stumbled upon an issue regarding performance.
My application uses a serverless approach with commercejs meaning I fetch my products and every other data via API calls instead of a traditional approach that involves me setting up a database and having other backend tools.
I already have this:
const App = () => {
const [products, setProducts ] = useState([]);
const getProducts = async () => {
const { data } = await commerce.products.list();
setProducts(data)
}
useEffect(() => {
getProducts()
},[])
}
Which is all used to get products and assign them to the products variable which I map through to display them inside divs and this works perfectly.
Here's what I need help with:
Is it be a good idea for me to use localStorage to store products instead of making a new commerce.products.list() every time the same user visits a page that need to display products?
Also, if so, how would one go about creating a function that knows when to update the products localStorage if there has been any changes (say a new product has been added or there's been price change for a certain product) if the products are now being fetched from localStorage?
Surely the answer to number 2, if is a yes, will be something like: make api requests that will be called on intervals but my main question/concern is how to know exactly when localStorage should be updated in an event like this without calling the API every now and then?
Also the use of sessionStorage did cross my mind, and it seemed like a better idea as the products data will be updated every time a user visits the application but I have considered the possibility of a user resuming a current window which was left open in the background for weeks and not see anything new.
I know this isn't a typical code/error/solution question but still any form of guidance/assistance will be highly appreciated.

Can I avoid a double-API call with NextJS' getServerSideProps?

I'm tinkering with NextJS' getServerSideProps. I see that when I request a page from scratch, I receive the fully hydrated content. Then when I navigate to a new page, an API call is made, which receives some JSON data that is used to re-populate the page.
What I don't like is that the new API call is actually making two calls. For example my getServerSideProps has an axios.get() call. So on that click to the new page, I'm getting:
a call to something like example.com/_next/data/1231234/....
that call, behind the scenes, must be running my getServerSideProps() with its axios.get() to retrieve the new JSON data.
So is there a way to avoid the double-API call? I'd prefer that after the first page load, clicks to new pages would just skip to step two.
On a non-NextJS app I'd have something like a useEffect() that ran on page load, but obviously then the first run of the page would not return the full content, and for search-engine purposes I'd like to return the full content. I've seen some lectures where Google says they do run javascript and see the full content, but might as well be on the safe side for all other engines.
getServerSideProps will always run at request time--whenever you hit the page (or possibly using prefetch, the default, of next/link) This will result in pre-render of the page using the data from getServerSideProps Side-note: If you using next API middleware, then you can avoid the ajax call and simply import the method to run directly in getServerSideProps.
It sounds like you want to fetch the data at build time and could render the page statically? If so, rather look to use getStaticProps.
You can also avoid both and make an API call in useEffect if you prefer, but code will be run at the client, once the page loads, of course. getServerSideProps will pre-render the page with the data before it renders to the client.
So, the goal is to determine ways of getting props for:
the initial (direct) page request,
in-app navigation request
To solve this we have two options. And unfortunately, both of them are not perfect.
First option:
Check if the request has header x-nextjs-data. NextJS adds this header for fetching data from getServerSideProps:
export const isInitialPageRequest = (req: GsspRequest) => {
return !req.headers['x-nextjs-data'];
}
export const getServerSideProps: GetServerSideProps = async (context: GetServerSidePropsContext) => {
if (isInitialPageRequest(context.req)) {
// this code runs only on the initial request, on the server side
}
return {
props: {
// props
}
}
}
In this case, the request to /_next/data/development/xxx.json?...' is still executed every time. But at least you can control behavior depending on the case (and avoid redundant API calls for example).
Second option:
Use getInitialProps and check if property context.req is defined or not. You already mentioned it in the comments, just added it as an answer option with an example:
page.getInitialProps = async (context: NextPageContext) => {
if (context.req) {
// this code runs only on the initial request, on the server side
}
return {
// props
}
}
The NextJS team is recommending to use getServerSideProps instead

Where should I load data from server in Redux + ReactJS?

For example I have two components - ListOfGroupsPage and GroupPage.
In ListOfGroupsPage I load list of groups from the server and store it to the state.groups
In route I have mapping like ‘group/:id’ for GroupPage
When this address is loaded, the app shows GroupPage, and here I get the data for group from state.groups (try to find group in state via id).
All works fine.
But if I reload page, I'm still on page /group/2, so GroupPage is shown. But state is empty, so the app can't find the group.
What is the proper way to load data in React + Redux? I can see this ways:
1) Load all data in root component. It will be very big overhead from traffic side
2) Don't rely on store, try to load required data on each component. It's more safe way. But I don't think that load the same data for each component - it's cool idea. Then we don't need the state - because each component will fetch the data from server
3) ??? Probably add some kind of checking in each component - first try to find required data in store. If can't - load from the server. But it requires much of logic in each component.
So, is there the best solution to fetch data from server in case of usage Redux + ReactJS?
One approach to this is to use redux-thunk to check if the data exist in the redux store and if not, send a server request to load the missing info.
Your GroupPage component will look something like
class GroupPage extends Component {
componentWillMount() {
const groupId = this.props.params.groupId
this.props.loadGroupPage(groupId);
}
...
}
And in your action...
const loadGroupPage = (groupId) => (dispatch, getState) => {
// check if data is in redux store
// assuming your state.groups is object with ids as property
const {
groups: {
[groupId]: groupPageData = false
}
} = getState();
if (!groupPageData) {
//fetch data from the server
dispatch(...)
}
}
I recommend caching the information on the client using localstorage. Persist your Redux state, or important parts of it, to localstorage on state change, and check for existing records in localstorage on load. Since the data would be on the client, it would be simple and quick to retrieve.
The way I approach this is to fetch from the server straight after the store has been created. I do this by dispatching actions. I also use thunks to set isFetching = true upon a *_REQUEST and set that back to false after a *_SUCCESS or *_FAILURE. This allows me to display the user things like a progress bar or spinner. I think you're probably overestimating the 'traffic' issue because it will be executed asynchronosly as long as you structure your components in a way that won't break if that particular part of the store is empty.
The issue you're seeing of "can't get groups of undefined" (you mentioned in a comment) is probably because you've got an object and are doing .groups on it. That object is most likely empty because it hasn't been populated. There are couple of things to consider here:
Using ternary operators in your components to check that someObject.groups isn't null; or
Detailing in the initialState for someObject.groups to be an empty array. That way if you were to do .map it would not error.
Use selectors to retrieve the list of groups and if someObject.groups is null return an empty array.
You can see an example of how I did this in a small test app. Have a look at specifically:
/src/index.js for the initial dispatch
/src/redux/modules/characters.js for the use of thunks
/src/redux/selectors/characters.js for the population of the comics, series, etc. which are used in the CharacterDetails component

Resources