getServerSideProps doesn't work redirect for all pages - javascript

I wan't for all pages if I has not token then redirect to /login
in _app,js file I added
export const getServerSideProps = async () => {
return {
props: {},
redirect: { destination: "/login" },
};
}
Then I run npm run build and start but getServerSideProps redirect dont wok for any pages
Maybe I created incorrect build not in
prod mode ?

Unfortunately, getServerSideProps is not supported in _app file right now. You can check this document for the reference.
But we can achieve the same thing with getInitialProps
App.getInitialProps = (ctx) => {
//TODO: Check authenticated value in cookies or somewhere in your code
const isAuthenticated = false
if(!isAuthenticated) {
if(typeof window === 'undefined'){ //server-side rendering
res.redirect('/login')
res.end()
return {}
} else { //client-side rendering
Router.push('/login')
return {}
}
}
return {
props: {}
}
}
Following this document again
Adding a custom getInitialProps in your App will disable Automatic Static Optimization in pages without Static Generation.
You need to consider this trade-off too.
If you want to handle it without these side effects. I'd suggest you use Context API. Your _app.js may be like this
const App = ({ Component, pageProps }) => {
return <AuthenticationProvider>
<Component {...pageProps}/>
</AuthenticationProvider>
}
After that, you can do some redirection stuff inside AuthenticationProvider that will apply to all pages

you don't need to use this for hard coding the "/login" you can easily write it in your component, the point of the getServerSideProps function is to fetch data from the backend and preload the data

Related

How to create a root component for making a request regardless of page url in next.js?

I am currently trying to make a request to server after refreshing page or opening this in a new tab.
For example after using f5 I want to make a request. But I don't want to make a request after every routing.
Basically after refreshing I want to make a request to see if user is authenticated using cookie on the request.
But It appears next.js doesn't have a root component like pure react.js(App.js). I searched documentations and different websited. But I couldn't find it. I don't want to write request code to every file.
Here is my files:
Is there a way to do this?
I want to do a request on every link possible (/login, /register, /profile, /) So I need a root component which is mounted on every link (only once).
You need to create _app.ts file in your pages directory. and add the following snippet.This component is the root component in next js and it any function you write here will be executed once depending on how you call this function.
for more info, check this link in Next Js docs https://nextjs.org/docs/advanced-features/custom-app
// import App from 'next/app'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
// Only uncomment this method if you have blocking data requirements for
// every single page in your application. This disables the ability to
// perform automatic static optimization, causing every page in your app to
// be server-side rendered.
//
// MyApp.getInitialProps = async (appContext) => {
// // calls page's `getInitialProps` and fills `appProps.pageProps`
// const appProps = await App.getInitialProps(appContext);
//
// return { ...appProps }
// }
export default MyApp
You can build a function to wrap around your "normal" pages to use it. You can pass arguments, in my example a country option.
export default function Layout(
{country, children}: PropsWithChildren<{country: Country }>) {
/* your code inside here */
return (
<main className={country} data-gtm-block="main">
{children}
</main>
);
}
and then use it like that for every page
export default function AboutPage({ country }: { country: Country }) {
return (
<Layout country={country}>
/* your code in here */
</Layout>
);
}
export const getStaticProps: GetStaticProps = async (context: GetStaticPropsContext) => {
const country = getCountry(context.locale);
return {
props: {
country
}
};
};

Does Nextjs generate duplicate pages at build time?

So I haven't found this answer anywhere and I would like to know this so that's why posting here. Suppose I have a blog for which I want to use SSG and ISR. SSG for the homepage and ISR for the individual posts.
So if I generated the homepage at build time with SSG to display 10 posts with the following code:
index.js file:
export const getStaticProps = async () => {
const posts = await get10PostsFromDB();
return {
props: {
posts
}
}
}
And then, if I use this code for each post and also use getStaticPaths to generate those exact 10 posts:
[post].js
export const getStaticPaths = async () => {
const posts = await get10PostsFromDB();
const paths = posts.map( ( item ) => ( {
params: {
slug: item.slug
}
} ) );
return {
paths,
fallback: 'blocking'
};
}
export const getStaticProps = async ({ params }) => {
const post = await getASinglePostFromDB( params.slug );
return {
props: {
post
},
revalidate: 1,
notFound: true
}
}
Will there be duplicate SSG generated pages as 10 are generated at build time for the homepage and then again those 10 pages are generated for getStaticPaths as well? Or what I'm thinking is totally wrong?
I think you have a bit of a misunderstanding of what getStaticPaths and getStaticProps do.
Short answer: no, it will not duplicate the pages.
Let me explain why:
getStaticProps vs getStaticPaths
getStaticProps (Static Generation): Fetch data at build time.
getStaticPaths (Static Generation): Specify dynamic routes to pre-render pages based on data.
What does getStaticProps do?
NextJS has this built-in page pre-rendering but this built-in process has a flaw if you wanna call it like this.
Take this code for example:
const HomePage = () => {
const [loadedPosts, setLoadedPosts] = useState();
useEffect(() => {
setLoadedPosts(DUMMY_POSTS);
}, []);
return (
loadedPosts ? (
<PostList posts={loadedPosts} />
) : ''
)
}
Note: This is a simple demo to convey the concept. In normal cases this issue will occur when you fetch data from an API in the useEffect hook.
Explanation: Let's imagine we are rendering a list of posts. DUMMY_POSTS is our data that comes from the db for example.
What happens here is that the useEffect hook will wait for the component to render before updating out loadedPosts.
When NextJS pre-renders a page it takes the snapshot of the first component rendering cycle as its content and that might be missing crucial data. This means that NextJS does not update the snapshot of our code AFTER the new data has come. This as you know is bad for SEO.
After this HTML page was received on the client, React will actually take over, the page is hydrated as this process is called, which means that now React will turn this into a single page application, take over control, and then this useEffect function will be executed, data might be fetched and the page might be updated - not on the server, not on the pre-rendered page but instead after this page was received in the browser. I.e no SSR.
Here's where getStaticProps() comes to play.
const HomePage = (props) => {
return (
<PostList posts={props.posts} />
)
}
export function getStaticProps() {
// fetch data from an API
return {
props: {
posts: DUMMY_POSTS
},
revalidate: 10
};
};
export default HomePage;
getStaticProps runs at build time and it fetches the data DUMMY_POSTS and then assigns it to props. This is then passed as props of the HomePage component where we can access props.posts.
What is getStaticPaths?
getStaticPaths is a function that you need to export in a page component file that is:
A dynamic page and
Is using getStaticProps
You DO NOT need it in a file that is using getServerProps and DO NOT NEED IT WITHOUT getStaticProps
Here I will use a very simple example to explain, but normally you generate the paths dynamically as you have done in your example:
export async function getStaticPaths() {
return {
fallback: false,
paths: [
{
params: {
postdId: 'p1'
}
},
{
params: {
postId: 'p2'
}
},
]
};
};
In the above example, we have two posts with IDs p1 and p2.
With getStaticProps a page is generated during the build process. This means that Next.js needs to pre-generate all versions for all dynamic pages in advance for all supported dynamic paths. Since it's dynamic Next.js needs to know for which IDs it needs to pre-generate a page. These functions are run during the build process NOT when a client visits the page. I.e. without getStaticPaths these dynamic pages will not be pre-generated on the server which kills the purpose of Next.js.
getStaticPaths tells Next.js what are the exact dynamic paths (in our case which post IDs) it needs to pre-generate. If the user enters an ID for which we did not pre-generate a page they will see a 404 error.
getStaticPaths returns an object where we describe all of the dynamic segment values. It has a paths property, which is an array that takes an object for each version of the dynamic page. This object has a params property, which is also an object that holds all of the key-value pairs that lead to your dynamic page.
TLDR;
getStaticProps fetches the data at build time before the component has loaded so that Next.js can pre-render the page WITH the data that we are fetching.
getStaticPaths tells Next.js about dynamic pages that need to be pre-rendered, because Next.js will not pre-render them on its own as it does not know exactly the dynamic IDs (routes). With this function you are basically telling Next.js how your dynamic routes are called so it knows what to pre-render. Otherwise it will show a 404 page.
Conclussion:
NextJS does not generate duplicate pages at build time.
EDIT:
In regards to pre-generating pages with getStaticPaths...
You DO NOT need to pre-generate every single dynamic route. In order to be able to load the other routes without a 404 error, you can add fallback: true or fallback: 'blocking'
These options can help in cases where you have a lot of pages that need to be pre-generated. In the case we have a website like Amazon with millions of products, pre-rendering all of them might take super long and there may be products that are rarely visited. Therefore, pre-generating such products is a waste of resources.
In such cases, we can set fallback: true and pre-generate some pages, not all.
export async function getStaticPaths() {
return {
fallback: true,
paths: [
{
params: {
prodId: 'p1'
}
}
]
}
}
Even if we don't list all of the pages, this setting tells NextJS that other pages can be valid and should be loaded when they are visited. They are generated just in time. This allows to pre-generate highly visited pages and pre-generate other pages only when they are needed.
However, if the request with the unsupported ID is sent directly (not internally through clicking on a product), it will return a 404. In such cases, we need to have a fallback case in our component.
const ProductDetailPage = ({ loadedProducts }) => {
if (!loadedProducts) {
return <p>Loading...</p>
}
return (
<>
<h1>{loadedProducts.title}</h1>
<p>{loadedProducts.description}</p>)
</>
)
}
In the above code, the user will see the Loading indicator instead of a 404 page, until the data comes and is then displayed on the page.
An alternative would be to set fallback: 'blocking'. In those cases, we don't need a fallback in the component, but the response will take a little longer.
const ProductDetailPage = ({ loadedProducts }) => {
return (
<>
<h1>{loadedProducts.title}</h1>
<p>{loadedProducts.description}</p>)
</>
)
}
export async function getStaticPaths() {
return {
fallback: 'blocking,
paths: [
{
params: {
prodId: 'p1'
}
}
]
}
}
It depends on your case. Sometimes you need to show something quickly (fallback: true). Sometimes it's worth waiting for it and you don't want to show an incomplete page to your visitors (fallback: 'blocking').
Read more about NextJS's Data fetching here.

Next.js: Skipping generating static sites at build time (instead: only at runtime, as getStaticProps has no data in build phase)

I want to use the same application for different customers, where there's a different database for each (which could be on-premise).
As a result, I have no data at the build phase (e.g. in CI/CD) that I could use to create static sites.
I thought about skipping generating the sites in getStaticProps via an environment variable.
When building the sites, I could tell Next.js to not use any data, something like this:
export const getStaticProps: GetStaticProps<HomePageProps> = async () => {
const isBuildPhase = process.env.IS_BUILD;
const data = isBuildPhase ? null : await fetchData();
return {
props: {
data: data ?? null,
},
revalidate: 5 * 60,
};
};
Now, I want to create the sites only at runtime (with next start), because at runtime, the built application has access to its database. In each getStaticProps the environment variable will be configured so data is fetched. When the application starts initially, it will generate all static sites when they're accessed.
Are there big downsides with this approach?
Are there maybe better solutions to this problem?
We had a similar requirement where the app needed to serve multiple tenants pages, but each tenant data is different. Which meant the app did not have access to data at build time and only runtime.
We leveraged getStaticPaths and getStaticProps to do this.
getStaticPaths
This method returns a fallback option (true || false || blocking), which we can use to decide to show a loader on the UX or block until the actual page loads.
export async function getStaticPaths() {
// Return empty paths because we don't want to generate anything on build
// { fallback: blocking } will server-render pages
// on-demand if the path doesn't exist.
return {
paths: [],
fallback: 'blocking',
};
}
When using this method, there isn't a need to maintain IS_BUILD env variable. (Depending on your specific use-case you may choose to use it or not)
getStaticProps
This method does the actual data fetching based on the URL path params for a tenant.
export async function getStaticProps({ params }) {
// Run your data fetching code here
const data = await fetch(params);
return {
props: data,
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every 10 seconds
revalidate: 10, // In seconds
notFound: !data,
};
}
We also leverage Incremental Static Regeneration, to make sure that we refresh the page every revalidate seconds.
Pages
Single Template Tenants
When all the tenants share the same components, it's pretty straight forward:
const SingleTenantPage = ({ data }) => {
return <Component {...data} />;
};
export default SingleTenantPage;
Multi-Template Tenants
The actual page, just parses the props (passed down from getStaticProps), and then loads the appropriate component for that page.
This way we leverage a single route for multiple tenants. example.com/app/tenantA, example.com/app/tenantB, example.com/app/tenantC all 3 routes can be served out of pages/app/[slug]/index.js.
Using dynamic imports, we make sure that only the component for a specific tenant is loaded. (also helps in code splitting)
// Dynamic Import so we load only the required bundles
const templates = {
tenantA: dynamic(() => import(`../templates/tenantA`)),
tenantB: dynamic(() => import(`../templates/tenantB`)),
tenantC: dynamic(() => import(`../templates/tenantC`)),
};
const MultiTenantPage = ({ data }) => {
// Provided template is present in the data
// template: 'tenantA' || 'tenantB' || 'tenantC'
const { template, ...rest } = data || {};
// If the template doesn't exist, show a 404 Page instead
const Component = templates[template] || (() => <Error statusCode="404" />);
return <Component {...rest} />;
};
export default MultiTenantPage;
The multi-tenant page works well for us. We use a larger revalidate value because data isn't changing that often. Only caveat we had were a little bit complex test cases because of all the dynamic imports.
The above solution is very much similar to what you've considered for your use-case, albeit without an additional env variable (IS_BUILD)

NextJS: Do I need to use getInitialProps with next-redux-wrapper to share state to all pages?

I'm using next-redux-wrapper and dispatching actions from getServerSideProps from individual pages. But I realized that I can't access the populated store state from another page. If I try to, in either client-side or server-side, the state returns empty in the other pages.
So, I heard that using getInitialProps is required to share state among all pages. Since I'm getting confused with all these I want to have some doubts cleared. I want to know:
When is it necessary, if at all, to use getInitialProps in the _app.js file when using redux with next-redux-wrapper? I heard that need to use getInitialProps inside _app.js in order to make the state accessible to every pages. But it's not working for me. Maybe due to wrong implementation!
If I use getInitialProps in _app.js then, is it not required to use getServerSideProps or getStaticProps in individual pages?
After populating state with getServerSideProps, can I share the state to every page without using getInitialProps in _app.js or if nneded can I pass the fetched state to getInitialProps in _app.js?
Yes, You have to use getIntitprops in the APP component to provide store in all pages in this case all page will run on a server which huge downfall, if you have lots of static pages,
or you can use this code on each page according to your needs but your dispatch will change server-side state only!!!, which means you can access them on the client-side.
export const getServerSideProps = wrapper.getServerSideProps(async ({ store, query }) => {
try {
const { id } = query
const res = await api.get('/abc', { params: { id } })
await store.dispatch(action)
return {
props: {
id,
data: res.data,
isServer: typeof window === 'undefined',
}
}
} catch (error) {
return {
props: {
errorCode: 409,
message: "Data Unavailable"
}
}
}
})
In the end, I ditched both options because it provides a bad user experience.
My recommendation is to use getInitProps and check if the page is rendering on the server then call API and save props in client-side, otherwise call API in the client a and save it.

Pattern for redirecting on unauthorized vuex actions

Navigation guards are perfect for redirecting unauthorized users to a login page, but what does one do to redirect unauthorized vuex actions to a login page?
I can do this easily enough in the vue method where I'm calling the action like so:
if (!this.isLoggedIn) {
this.$router.push({ name: 'login', query: { comeBack: true } })
$(`.modal`).modal('hide')
return
}
But then I'm inserting these 5 lines of code for every component method that requires authorization.
All the solutions I can think of sound hacky, so I'm wondering what the vuex way is:
In order to reject it at the vuex action level, I have to pass up the $router instance, and I'm still reusing the 5 lines for each action that requires auth.
I can handle this in a utility file, but then I'm handling $router instance in that file.
I can use a global vue mixin and call it (a) before making a call and then again (b) when getting a 401 back from the server.
All those seem odd. What vuex way am I missing here?
This sounds like a job for middleware. Unfortunately, Vuex doesn't have an official way to do middleware.
There is a subscribeAction() but that runs after the commit, so does not allow mods to the action. There is also a proposal Middleware processing between actions and mutation.
As I see it, we want middleware to be able to do two generic things
cancel the action
allow alternative actions to be called
The second is difficult to do without patching store.dispatch() or messing with the private property _actions after store has been created.
However, to guard an action as you describe, we only need to be able to cancel it.
Here is a poor-man's middleware for the modules pattern for Vuex store which I prefer.
store construction from modules
export const store = new Vuex.Store({
modules: {
config,
pages: applyMiddleware(pages),
measures,
user,
loadStatus,
search
}
})
applyMiddleware
const applyMiddleware = function(module) {
if(module.middlewares) {
Object.values(module.middlewares).forEach(middlewareFn => {
Object.keys(module.actions).forEach(actionName => {
const actionFn = module.actions[actionName]
module.actions[actionName] = addMiddleware(actionName, actionFn, middlewareFn)
});
})
}
return module;
}
addMiddleware
const addMiddleware = function(actionName, actionFn, middlewareFn) {
return function(context, payload) {
const resultFn = middlewareFn(actionFn)
if(resultFn) {
resultFn(context, payload)
}
}
}
defining middleware in the module
const actions = {
myAction: (context, payload) => {
...
context.commit('THE_ACTION', payload)
...
},
}
const middlewares = {
checkAuthMiddleware: (action) => {
return this.isLoggedIn
? action // if logged-in run this action
: null; // otherwise cancel it
}
}
export default {
state,
getters,
mutations,
actions,
middlewares
}
This implementation has module-specific middleware functions, but you could also define them globally and apply to as many modules as applicable.

Categories