Problem:
Revalidate doesn't work when im useing (serverSideTranslations) from next-i18next. Please see my code below.
When a new user register, an static page is generated for each language: (da (Default), en, sv, de).
Everyting works fine with the code below, for allready generated pages at build time (When deployed on Vercel).
When a new user register, the page wont get revalidated and thorws a 500 Internal server error. / The page was not generated
Please see comment in code!
export async function getStaticPaths({ locales }) {
const { data: profiles, error } = await supabase.from("profiles").select("*");
console.log(locales);
const paths = profiles
.map((profile) =>
locales.map((locale) => ({
params: { profileName: profile.username },
locale, // Pass locale here
}))
)
.flat();
console.log(paths);
return { paths, fallback: "blocking" };
}
export async function getStaticProps({ params, locale }) {
const { data: profiles, error } = await supabase
.from("profiles")
.select("*")
.eq("username", params.profileName)
.single();
return {
props: {
profiles,
...(await serverSideTranslations(locale, ["Profil"])), //When this line is removed everything works fine --- When its added GetStaticPaths and GetStaticProps breaks.
},
revalidate: 30,
};
}
Related
I'm trying to deploy my first next.js project on vercel. Locally everything is working.
My problem: When deploying (command: next build) the web app I get the message "Generating static sites (0/2000)" and then nothing happens. The deployment is cancelled after 1h (time expiration).
The problem is somewhere in the (simplified) code below. Here's why: when I deploy the project without the part that follows after const content the deployment is successful - so basically instead of having singleProductResponse AND contentResponse as props, I only have singleProductResponse..
I'm a little stuck and don't know how to solve this. Can someone tell me what I'm doing wrong? Thanks a lot!!
const Item = ({ singleProductResponse, contentResponse }) => {
const router = useRouter();
if (router.isFallback) {
return <div>Loading...</div>;
}
return (
<div className={styles.section}>
<Overview
singleProductResponse={singleProductResponse}
contentResponse={contentResponse}
/>
</div>
);
};
export async function getStaticPaths() {
const itemResponse = await knex("Items");
const paths = itemResponse.map((product) => ({
params: {
brand: product.brand,
item: product.item,
},
}));
return {
paths,
fallback: true,
};
}
export async function getStaticProps({ params }) {
try {
const itemData = await knex("Items").where(
"item",
"like",
`${params.item}`
);
const singleProductResponse = itemData[0];
//!!!!!!!!!when leaving the following part out: deployment is successful!!!!!!!!!!!!
const content = itemData[0].contentList;
const splitContent = content.split(", ");
const contentArray =
typeof splitContent === "string"
? [splitContent]
: splitContent;
const result = await knex("Cos")
.leftJoin("Actives", "Cos.content", "ilike", "Actives.content")
.leftJoin("Alcohol", "Cos.content", "ilike", "Alcohol.content")
.column([
"Cos.content",
"function",
"translationPlant",
"categoryAlcohol",
])
.where((qb) => {
contentArray.forEach((word) => {
qb.orWhere("Cos.content", word);
});
return qb;
});
return {
props: { singleProductResponse, contentResponse },
revalidate: 1,
};
} catch (err) {
console.log(err);
return {
redirect: {
destination: "/",
permanent: false,
},
};
}
}
UPDATE
After digging deeper: I think the problem is that the part after const content is too slow in building process.
When running next build, as well as when deploying on vercel the building process stops after approx. 1h. I suppose this is because of the limit of 45min for building process.
The last message I get is "Generating static pages (1000/2000)" (in vercel and locally) and "Build failed" (vercel). I don't get any other error-messages (also not in the catch block).
I've already tried to optimize the part after const content (each table has an index (clustered indexes -> primary keys), I've redesigned the tables (only 4 tables to join, instead of 6), eliminated everything unnecessary in the query and checked that the database (postgres hosted on heroku - hobby-basic) is also in the US). The performance is better, but still not enough. Does anyone have some suggestions for improvement? TTFB might be a somehow slow.
I am using vercel for NextJS and this is my setup in getStaticPaths
const paths = posts.map((post) => ({
params: { player: post.player, id: post.id },
}))
return { paths, fallback: true }
When I set the fallback to true, I have got this error in vercel:
21:55:01.736 info - Generating static pages (1752/1752)
21:55:01.736 > Build error occurred 21:55:01.739 Error: Export
encountered errors on following paths: 21:55:01.739
/clip/[player]/[id]
It is ok when fallback is set to false but I really like to set fallback set to true so that pages can be updated frequently. Any help will be greatly appreciated...
Inside your /clip/[player]/[id].js file, you need to handle the fallback state when that page is being requested on-demand.
// pages/posts/[id].js
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
}
// This function gets called at build time
export async function getStaticPaths() {
return {
// Only `/posts/1` and `/posts/2` are generated at build time
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
// Enable statically generating additional pages
// For example: `/posts/3`
fallback: true,
}
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return {
props: { post },
// Re-generate the post at most once per second
// if a request comes in
revalidate: 1,
}
}
export default Post
https://nextjs.org/docs/basic-features/data-fetching#fallback-true
What I did was conditionally render my component. So, my component receives the object data and if I need to use a value from data, such as "title", I will do...
data?.title
Also, for my entire return component I will conditionally render it. For example...
{data !== undefined ? (
<div className ='main-content'>
<p> This is the content that I want rendered if data is defined </p>
</div>
) : (
<div className = 'fallback-content'>
<p> This shows if data == undefined </p>
</div>
)
I have a [slug].js page that will fetch API to get the destination page
export async function getServerSideProps({ query, res }) {
const slug = query.slug;
try {
const destination = await RoutingAPI.matchSlug(slug);
res.writeHead(302, { Location: destination });
res.end();
// return {
// redirect: {
// permanent: true,
// destination,
// },
// }
} catch (error) {
return {
notFound: true
}
}
}
If I client redirect from another page to slug page, it works and keeps URL the same as slug but it makes the browser reload. If I use
return {
redirect: {
permanent: true,
destination,
},
}
it will not reload the browser but it change URL to the destination, not the same as slug. How do i fix this problem? I would appreciate any ideas, thanks
You can use rewrites to achieve this. From the docs:
Rewrites allow you to map an incoming request path to a different destination path.
In your next.config.js add this:
module.exports = {
async rewrites() {
return [
{
source: "/:slug",
destination: "/your-destination",
},
];
},
};
rewrites is an async function that expects an array to be returned holding objects with source and destination properties:
source is the incoming request path pattern.
destination is the path you want to route to.
Ok, I have a solution, it doesn't reload and it doesn't change the url.. however it requires some client side script.. as you see in the example below
Hope you find it to your liking :D
Here's my codesandbox
index.js(from your codesandbox)
import Link from "next/link";
export default function IndexPage() {
return (
<div>
Hello World.{" "}
<Link href="/something-slug">
<a id="myElem">Go to slug pageee</a>
</Link>
<script
dangerouslySetInnerHTML={{
__html: `
let elem=document.getElementById('myElem')
elem.addEventListener('click',async function(event){
event.preventDefault()
console.log(event.path[0].href)
let myFetch=await fetch(event.path[0].href)
let text=await myFetch.text()
let newHTML=document.createElement('html')
newHTML.innerHTML=text
let _next=document.getElementById('__next')
_next.innerHTML=""
let requestedNext=[...newHTML.getElementsByTagName('div')].filter(a=>a.id=="__next")[0]
let scripts=[...requestedNext.getElementsByTagName('script')]
scripts.forEach(a=>eval(a.innerHTML)) //eval any scripts sent by YOUR requested _next
console.log(requestedNext)
_next.innerHTML=requestedNext.innerHTML
})
`
}}
></script>
</div>
);
}
slug.js(again, from your codesandbox)
export default function AboutPage() {
return <div>About us</div>;
}
export async function getServerSideProps({ query, req, res }) {
return {};
}
I'm struggling with the configuration of auth + Axios.
I'm currently working on our social login (FB and google). It half works.
First of all, I have my Axios instance configured as a plugin. We do have two instances, one that we use for general API requests and another one that we will use for logged user requests.
plugins/axios.js
export default function({ $axios, redirect, app }) {
$axios.defaults.baseURL = process.env.baseUrl
$axios.defaults.headers = {
Authorization: `Bearer ${process.env.APIKey}`,
Accept: 'application/json',
'Content-Type': 'application/json',
lang: app.i18n.locale
}
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 400) {
redirect('/400')
}
if (code === 404) {
redirect('/404')
}
if (code === 500) {
redirect('/500')
}
})
}
plugins/auth-axios.js
export default function({ $axios }, inject) {
const authAxios = $axios.create()
// authAxios.setToken('123', 'Bearer')
inject('authAxios', authAxios)
}
The first client works perfectly in the whole app.
Now I'm configuring the auth plugin. When I log in through Facebook or google I need to take the response data of the logged user from the social network and send it to my API, expecting a user with the token as the response. This will be the user that I'll set in the auth plugin. Every time I log in with Facebook it appears to work well except when It arrives at the plugin code. $auth.loggedIn is always false.
I've made it work forcing $auth.fetchUser at the beginning of the code but it doesn't work well at all. It shows an Axios error when console.log($auth). This is my auth code:
plugins/auth.js
export default async function({ app: { $auth, $axios } }) {
$auth.fetchUser()
console.log($auth)
if (!$auth.loggedIn) {
return
}
const authStrategy = $auth.strategy.name
if (authStrategy === 'facebook' || authStrategy === 'google') {
if ($auth.user.google_id || $auth.user.fb_id) return
try {
const url = `/client/social`
var postData
if (authStrategy == 'facebook')
postData = {
name: $auth.user.name,
email: $auth.user.email,
fb_id: $auth.user.id,
avatar: $auth.user.picture.data.url,
birthday: $auth.user.birthday
}
else
postData = {
name: $auth.user.given_name,
surname: $auth.user.family_name,
email: $auth.user.email,
google_id: $auth.user.sub,
avatar: $auth.user.picture,
locale: $auth.user.locale
}
const { data } = await $axios.post(url, postData)
$auth.setToken('client', data.access_token)
$auth.setUser(data.client)
} catch (e) {
console.log(e)
}
}
}
The console.log($auth) error:
'$state': {
user: null,
loggedIn: false,
strategy: 'facebook'
},
error: TypeError: Cannot set property 'Authorization' of undefined
at Function.setHeader (server.js:1556:42)
at Oauth2Scheme._setToken (server.js:1014:31)
at Oauth2Scheme.mounted (server.js:1001:12)
at Auth.mounted (server.js:516:42)
at Auth.init (server.js:459:18)
at module.exports../.nuxt/auth/plugin.js._webpack_exports_.default (server.js:939:16)
at createApp (server.js:2432:87)
}
And my auth module config:
import dotenv from 'dotenv'
dotenv.config()
export const auth = {
plugins: [
// {
// src: '~/plugins/axios',
// ssr: true
// },
{
src: '~/plugins/auth-axios',
ssr: true
},
'~/plugins/auth.js'
],
redirect: {
login: '/',
callback: '/callback'
},
strategies: {
local: false,
facebook: {
client_id: '#############',
userinfo_endpoint:
'https://graph.facebook.com/v2.12/me?fields=about,name,picture{url},email,birthday',
scope: ['public_profile', 'email', 'user_birthday']
},
google: {
client_id:
'#####################'
}
}
}
Seems that auth when login is trying to set the Axios token (also when I log out it tries to remove it) but it fails. If I go to the Chrome dev tools and debug it to see which Axios instance is trying to use for that. Every time is the main Axios instance and it's supposed to be accessible there.
Screenshot from DevTools:
Screenshot from DevTools
Does someone know what I'm missing? Can I prevent auth facebook strategy to update any Axios instance? Or, can I specify which Axios instance to update (set / remove token)?
EDIT: Forcing the $auth.fetchUser my auth plugin code does work but with the error mentioned before. When I try to logOut it doesn't work due to the same error when trying to remove the token automatically)
I'm going crazy with this issue for two weeks now.
Thanks so much!
I'm starting with Next.js and after going through docs, I cannot figure out how to get the route param code inside getStaticPaths method as shown below!?. code is not known before hand by any means and it can be anything.
I don't want to call api and get the data using useEffect inside the component.
File: pages/post/[code].js
import React from 'react';
import apiCall from 'api/something';
export default ({post}) => {
return <>
render components here based on prop `post`
</>
}
export async function getStaticPaths() {
// How to get [code] from the route here, which can be used below?
return {
paths: // NEED [code] HERE from current route,
fallback: false
}
}
export async function getStaticProps(ctx) {
return {
props: {
// [ctx.code] resolved from current route with the help of getStaticPaths,
post: apiCall(ctx.code)
}
}
}
I've tried getServerSideProps which works for me:
export const getServerSideProps = async (ctx) => {
return {
props: {
post: await apiCall(ctx.query.code)
}
};
};
But it fails when I do next export stating:
pages with getServerSideProps can not be exported. See more info here: https://err.sh/next.js/gssp-export
After investigating further on this error I found this solution, which is not feasible for me as my app is hosted on Heroku.
I'm trying to server-side render the html along with the data based on the route param code. But not able to do so now.
The purpose of the function getStaticPaths is to generate a list of paths for which static HTML will be rendered at build time. For example, for a list of 10 posts, you can generate 10 posts/[id] routes ahead of time if you know the id of the posts.
How getStaticPaths works with dynamic routes in more details..
Suppose you have a dynamic route /posts/[postId] if you choose to use static-generation you have to generate a list of paths that will include the postId as a route param and for each path returned, the function getStaticProps will be called to query the data at build time. Example,
// for /post/[postId]
export const getStaticPaths = async () => {
// if you know all the postId ahead of time
const paths = [
{ params: { postId: '1234' } }, // keep in mind postId has to be a string
{ params: { postId: '3792' } },
{ params: { postId: '1749' } },
]
return {
paths,
fallback: false // we are disabling fallback because we know all the paths ahead of time
}
}
// for each path returned getStaticProps will be called at build time
export const getStaticProps = async (context) => {
// you have access to the postId params that you returns from
// getStaticPaths here
const postId = context.params.postId
// now you can query the data from postId and return as props
return {
props: // queried data
}
}
If fallback is set to false any for any route path that is not returned from the function getStaticPaths nextjs will simply show a 404 error page.
How to use fallback: true to generate static pages for route params not known ahead of time
If you know some postId of the posts and the data for the posts do not change very often, you can choose to generate the pages with fallback property set to true, which will display a fallback version of the page for the paths that are not returned from the function getStaticPaths. And on request for the page nextjs will call getStaticProps and send the data as JSON which will be used to render the page in the browser.
Example,
// for /post/[postId]
export const getStaticPaths = async () => {
// you can get how many ever postIds are know ahead of time
// and return as paths with fallback set to true
const posts = // queried data from db or fetched from remote API
const paths = posts.map(post => { params:{ postId: post.id.toString() }})
return {
paths,
fallback: true
}
}
// in your page Component check for fallback and render a loading indicator
import { useRouter } from 'next/router';
const MyPage = (props) => {
// before you do anything
const router = useRouter();
if (router.isFallback) {
return <div>Loading....</div>
}
// rest of your page logic
}
If your data is very dynamic, let's say changing every 30mins or an hour or so. You can choose to use server-side rendering which will fetch the data on per request basis, but TTFB(time to first byte) will be higher. For example,
// for /post/[postId]
export const getServerSideProps = async (context) => {
// you also have access to the param postId from the context
const postId = context.params.postId
// query the data based on the postId and return as props
return {
props: // queried data
}
}
Keep in mind if you choose to go with getServerSideProps the function will be called on per-request basis so time to first byte will be higher.
Depending on use-cases you can also use static generation with client-side data fetching using swr from nextjs team repo link.
As I understand, you want to statically generate dynamic routes at build time.
To do so you need to let Next.js know what pages to generate, by specifying all codes.
export async function getStaticPaths() {
// you don't need here a code from current route
// but you need to specify all known post codes
return {
paths: [
{ params: { code: '1' } },
{ params: { code: '2' } },
{ params: { code: '3' } },
]
fallback: false
}
}
You would need to re-build app every time you change the posts.
Use getServerSideProps if you don't want to re-build project every time. Then the data would be fetched at request time. You can't export it because it requires Node.js server.