Next.js rendering strategy for unique dynamic route - javascript

I want to make the best use of the server where my Next.js web app will be hosted, even if it is at the cost of the APIs where users get informations.
So I was wondering what was the best approach to render uniques dynamic routes, for example: /post/[postId].
I want to avoid SSR and have static HTML files hydrated by APIs as often as possible, as I've made for /home/[page] where I've done some ISR to avoid frequent rerenders like this:
export async function getStaticProps(context = {}) {
return {
props: {},
revalidate: 120, //cache page for 120s
}
}
// No prerender of paths <=> "paths: []"
export async function getStaticPaths(context = {}) {
return {
paths: [],
fallback: 'blocking'
}
}
The problem for /post/[postId] being that postId is a unique identifier so caching the page has no real interest and prerendering is not possible.
The thing is /post/id1 and /post/id2 have no real HTML differences because the [postId] property is only used in a useEffect to fetch data so a SSR is a complete waste of server ressources..
So the question is what could be a way to optimise Next.js rendering uniques dynamics routes ? Any idea is welcomed !

I guess one way is to use dynamic imports. This will decrease your bundle size and introduce lazy loading for your JavaScript code. One note with static HTML pages is that they are small in size so not a lot of optimization is needed.
const SomePage = dynamic(
() => import('#modules/some-page/index'),
{
ssr: false,
}
);

Related

Statically pre-render most popular queries with NextJS [duplicate]

This question already has an answer here:
How to add new pages without rebuilding an app with +150k static pages?
(1 answer)
Closed 10 months ago.
I am using NextJS to pre-render pages of the format:
./pages/folder/[variable].js
I have a set of some ~ 8000 possible values for [variable], so it is not practically feasible to statically generate all 8000 of these pages. Because of this, I use getSeverSideProps() whenever this page is loaded. Here is what that looks like:
export async function getServerSideProps(context) {
const { variable } = context.params
const { res } = context;
// Caches response for 15 minutes on the server side
res.setHeader('Cache-Control', `s-maxage=900, stale-while-revalidate`)
const { data } = await axios.get(url + variable);
return { props : {data} }
}
In spite of there being over ~ 8000 values, the top 50 values contain ~90% of queries.
Is there any way to statically pre-render these 50 values with something like getStaticPaths() and getStaticProps(), such that these particular 50 queries are faster to load?
Secondarily, and less concerning, is that the list of the most common requests changes slightly over time. If there was a solution to dynamically determine this list of 50 values, that would be ideal (although is not strictly necessary).
This is exactly the concept of Incremental Static Regeneration in Nextjs:
Next.js allows you to create or update static pages after you’ve built your site. Incremental Static Regeneration (ISR) enables you to use static-generation on a per-page basis, without needing to rebuild the entire site. With ISR, you can retain the benefits of static while scaling to millions of pages.
and here is how it works:
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
export async function getStaticPaths() {
const res = await fetch('https://.../posts')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// We'll pre-render only these paths at build time.
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
return { paths, fallback: 'blocking' }
}
Don't forget that you still need to define getStaticProps function on your page.
Here is the official Documentation about Incremental Static Regeneration

How to create and call selectors from a separate file in Cypress?

I am new to Cypress and struggling to get it work. I need to create a file with selectors (I suppose in 'support' folder) to use them in my project file.
Here is an example
describe('Test_spec_1', () => {
it.only('Visits the site & verifies elements', () => {
cy.get('[type=text]').should('be.visible')
cy.get('[type=password]').should('be.visible')
cy.get('[type=submit]').should('be.visible')
cy.get('[routerlink="/login"]').should('be.visible')
cy.get('[routerlink="/reset-password"]').should('be.visible')
cy.get('[routerlink="/support"]').should('be.visible')
cy.get('[routerlink="/reset-password"]').should('be.visible')
})
})
Basically, I need to have all selectors in a separate file, so I can call them and update their values easily.
I have experimented a bit with export/import, but it did not work. Could't find anywhere how to use it properly. It would be great if you can give me some hints how to do it. Thank you very much.
You can create a folder called page-objects inside your integration folder. Inside that you can create one js file for each screen like login.js. Now inside that you can write your locators like:
class login {
usernameInput() {
return cy.get('[type=text]')
}
passwordInput() {
return cy.get('[type=password]')
}
submitButton() {
return cy.get('[type=submit]')
}
}
export default login
Inside your tests you can use them as:
import login from '/page-objects/login.js'
const loginPage = new login();
describe('Test_spec_1', function() {
it('Visits the site & verifies elements', function() {
loginPage.usernameInput().should('be.visible')
loginPage.passwordInput().should('be.visible')
loginPage.submitButton().should('be.visible')
})
})
Please don't use page objects in Cypress, see this tutorial Stop using Page Objects
Page objects problems
Page objects are hard to maintain and take away time from actual application development. I have never seen PageObjects documented well enough to actually help one write tests.
Page objects introduce additional state into the tests, which is separate from the application’s internal state. This makes understanding the tests and failures harder.
Page objects try to fit multiple cases into a uniform interface, falling back to conditional logic - a huge anti-pattern in our opinion.
Page objects make tests slow because they force the tests to always go through the application user interface.
Number 3) killed it for me. You get to a point where you try to figure out more convoluted methods in the page object to cater for different scenarios.
The easiest way to store your selector text in one place is given in this question Where to store selectors in Cypress.io
// cypress/support/selectors.js
export default {
mySelector: '.my-selector',
mySelector2: '.my-selector-2'
};
// cypress/integration/one.spec.js
import selectors from '../support/selectors.js';
describe('test', () => {
it('test', () => {
cy.get(selectors.mySelector);
});
});

Next.js: getStaticProps not updating fetch values in production

I'm basically developing a blog on Next.js. Because it is another team which is in charge of the back-end, I'm currently making fetch API calls from getStaticProps to get my articles even though it's better practice to make database queries directly:
export async function getStaticProps({ params, res }) {
try {
const result = await fetch(`${API.baseURL}/get_article/${params.id}`);
const article = await result.json();
return {
props: { article },
};
} catch (error) {
res.statusCode = 404;
return { props: {} };
}
}
While this works perfectly in development mode, getting the article, editing it, and then accessing it again does not work in production (even locally, with the built version).
I guess it has something to do with Next.js handling cache somehow... What am I doing wrong? Thank you!
First of all the argument of function getStaticProps i.e the context object doesn't have any property named res. So res.statusCode = 404; doesn't do anything here.
And getStaticProps is meant be used for static site generation, additionally for dynamic routes, you can export another function getStaticPaths which should generate and return an array of paths with the dynamic route params for which getStaticProps will be called at build time for pre-rendering the pages.
In development mode, data-fetching methods will be called per-request basis so your code works. But in production mode, it will show the pre-rendered static pages which means the page will show the content as it was rendered and if you edit and update the content it will not reflect on the pages.
If you decide to go with static-site-generation either you have to rebuild the entire site after an update to a blog or you have to have some kind of client-side data-fetching logic that will update the blog when you update its content.
For client-side data-fetching you can use something like swr or react-query
Here is some psuedo-code which might help with pre-rendering the pages,
for route /article/[articleId]
export async function getStaticPaths() {
const articles = await /* db query to get the list of articles or fetch from remote API*/
// generate a list of paths with route params
const paths = articles.map(article => ({ params: { articleId: article.id }}))
return {
paths,
fallback: false
// fallback can be true if you want to show a fallback version of page
// and serve JSON for unknown articles
}
}
export async function getStaticProps(ctx) {
try {
const result = await fetch(`${API.baseURL}/get_article/${params.id}`);
const article = await result.json();
return {
props: { article },
};
} catch (error) {
return {
props: null
}
}
}
Learn more about how fallback works in returned value of function getStaticPaths docs.
Another alternative is to use getServerSideProps as the data-fetching method which will be called on each request for the page, but TTFB(time to first byte) will be higher. So for a blog-post site, I will not suggest using getServerSideProps.
You have to add the revalidate parameter. Find out more here.
In your case
export async function getStaticProps({ params, res }) {
try {
const result = await fetch(`${API.baseURL}/get_article/${params.id}`);
const article = await result.json();
return {
props: { article },
revalidate: 10 // 10 seconds
};
} catch (error) {
res.statusCode = 404;
return { props: {} };
}
}
Please note that revalidate parameter.
There is a way to update the HTML that is generated using getStaticProps, this process is called incremental server regeneration. This will ensure that your page is updated whenever you push an update to your blog post.
NextJS has documented this
https://nextjs.org/docs/basic-features/data-fetching
You search for ISR on the above page to understand how it's done
Basically you'll have to specify a time after which NextJS will try to update the page and in case there is a new post altogether, it will be server rendered on first request and then cached, once cached it'll work almost like a static page, refer fallback: 'blocking'
Quoting from website :
Next.js allows you to create or update static pages after you’ve built your site. Incremental Static Regeneration (ISR) enables you to use static-generation on a per-page basis, without needing to rebuild the entire site. With ISR, you can retain the benefits of static while scaling to millions of pages.
Consider our previous getStaticProps example, but now with Incremental Static Regeneration enabled through the revalidate property

Gatsby: Why Do I (Or Do I Even?) Need to Use exports.onCreateNode to Create Pages?

All of the examples in the Gatsby documentation seem to assume you want to define an exports.onCreateNode first to parse your data, and then define a separate exports.createPages to do your routing.
However, that seems needlessly complex. A much simpler option would seem to be to just use the graphql option provided to createPages:
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions;
const { data } = await graphql(query);
// use data to build page
data.someArray.forEach(datum =>
createPage({ path: `/some/path/${datum.foo}`, component: SomeComponent }));
However, when I do that, I get an error:
TypeError: filepath.includes is not a function
I assume this is because my path prop for createPage is a string and it should be "slug". However, all the approaches for generating slugs seem to involve doing that whole exports.onCreateNode thing.
Am I missing a simple solution for generating valid slugs from a path string? Or am I misunderstanding Gatsby, and for some reason I need to use onCreateNode every time I use createPage?
It turns out the error I mentioned:
TypeError: filepath.includes is not a function
Wasn't coming from the path prop at all: it was coming from the (terribly named) component prop ... which does not take a component function/class! Instead it takes a path to a component (why they don't call the prop componentPath is just beyond me!)
But all that aside, once I fixed "component" to (sigh) no longer be a component, I was able to get past that error and create pages ... and it turns out the whole onCreateNode thing is unnecessary.
Why Do I Need to Use exports.onCreateNode to Create Pages?
You do not.
Gatsby heavily uses GraphQL behind the scenes. The Gatsby documentation is about teaching users that many of the features in Gatsby are often only available via GraphQL.
You can create pages without GraphQL as you do in answer with data.someArray.forEach ... but that is not the intended way. By skipping createNodeField you will not be able to query for these fields within your page queries. If you don't need these fields via GraphQL then your solution is perfect.

Advice about webapp architecture and reactJs

This question is more to know your opinions about the way I'm trying to solve this issue.
I would need a little bit of ReactJs expertise here as I'm quite new.
First a little bit of context. I'm developing a web application using ReactJs for the frontend part.
This webapp is going to have many translations, so for maintenance I thought it would be better to store all the translations in a database instead of having them into a file. This way I could manage them using sql scripts.
I'm using a MySQL database for the backend, but for performance reasons, I have added ElasticSearch as second database (well, it is more a full text search engine).
So once the application starts, the translations are automatically loaded into ElasticSearch. Every translation has a code, and a text, so in elastic search I only load the translations for one locale (by default english), and when a user switchs the locale, a call is done to load all the translations for the selected locale and update their corresponding text.
This way from the fronted I can reference a translation only by the code and I will get the text translated in the correct locale.
Now, how do I do that in react?
So far I have written a component TranslatedMessage which is basically looking for a given code and displaying it whereever this component is rendered.
Below the code of the component:
import React from 'react';
export class TranslatedMessage extends React.Component {
constructor() {
super();
this.render = this.render.bind(this);
this.componentDidMount = this.componentDidMount.bind(this);
this.state = {message: ''};
}
render() {
return (<div>{this.state.message}</div>);
}
componentDidMount() {
var component = this;
var code=this.props.code;
var url="data/translation?code="+code;
$.get(url, function (result) {
component.setState({message: result.text});
});
}
};
And then I use it in the application whis way, for example to translate the title of an 'a' link:
<TranslatedMessage code="lng.dropdown.home"/><i className="fa fa-chevron-down" />
So far is working fine but the problem is that I need to refresh the whole page to get the new translations displayed, because I'm not updating the state of the component.
So now my questions:
1)Every time that we find in a page the component TranslatedMessage, a new instance of that component is created right? so basically if I have 1000 translations, 1000 instances of that component will be created? And then React has to take care and watch all these instances for changes in the state? Would that be very bad for performance? Do you find any more efficient way to do it?
2) I don't think forcing the whole page to reload is the most proper way to do it, but how can I update the states of all that components when a user switch the locale? I've been reading about a framework (or pattern) called Flux, and maybe that could fit my needs, what do you thing, would you recommend it?
3) What do you think about storing translations on db, I'm a bit concern about sending a query to the db for every translation, would you recommend or not this approach?
Any suggestions, ideas for improvement are very welcome!
Thank you for taking your time to read it or for any help!
I use what is basically a Flux store for this purpose. On initialisation the application requests the whole language file to use (which is JSON) and that gets shoved into memory in the store, something like this (I'm going to assume a totally flat language file for now, and I'm using ES2015/16 syntax, I've omitted error checking etc etc for brevity):
class I18n {
constructor() {
this.lang = await fetch( 'lang_endpoint' )
.then( res => res.json() )
}
get( translation ) {
return this.lang[ translation ] || null
}
}
Somewhere my app starts during a ReactDOM.render( <App /> ) or some variation and this renders the whole thing top-down (I try to eliminate state as much as possible). If I needed to switch languages then I'd bind a change handler such that the store emits a change event which is heard by some code that triggers a ReactDOM.render. This is fairly standard Flux practise for changing the app state, the key is to try and eliminate state from your components and store it inside your stores.
To use the I18n class simply instantiate it somewhere (I normally have it as a singleton exported from a file, e.g. module.exports = new I18n(), require that file into your components and use the get method (this assumes some sort of packager such as browserify or webpack but it looks like you have that complexity all sorted):
import 'i18n' from 'stores/i18n'
class MyComponent extends React.Component {
constructor() { ... }
render() {
return (
<span>{ i18n.get( 'title' ) }</span>
)
}
}
This component could also be simplified to
const MyComponent = props => <span>{ i18n.get( 'title' ) }</span>

Categories

Resources