getStaticProps runs on every request in production - reactjs

I use Next JS in my project. I want do only one request in page. And in next docs says that using get Static Props is that's what I need. But in doesnt work for me.
This is my code
export async function getStaticPaths() {
const leaguesCol = await collection(database, 'leagues');
const leaguesSnapshot = await getDocs(leaguesCol);
const leagues = leaguesSnapshot.docs.map(doc => doc.data());
return {
paths: leagues.map((item) => ({
params: {
id: item.link,
},
})),
fallback: false,
};
}
export async function getStaticProps() {
const { id } = context.params;
const leaguesRef = await collection(database, 'highlights');
const q = query(leaguesRef, where('league', '==', id));
const leagueSnapshot = await getDocs(q);
const data = leagueSnapshot.docs.map(doc => doc.data());
return {
props: { data },
};
}
But, when i deploy project in Firebase, i see that request happens in every routing to page. For example, in screen my routing between "spain" and "germany" pages
enter image description here

Related

Use CSR in Next.js as fallback when SSR path is not available

So I have this blog, with dynamic url for posts (blog/[postId]). Posts are being added through keystone.js cms. It worked fine via CSR, but i came to a necessity to add proper meta open graph tags for each post (for correct share snippets).
I added getStaticProps and getStaticPaths to my post page, and now my meta tags are working fine, but now i get 404 when trying to access any post, that was added after build. Meaning, now i have to rebuild after each new post. My question is - is it possible to set Next to render component as CSR, if it fails to find path for it with getStaticPaths? I've tried to use try catch, but that didn't work. Now i came up with this concept of using container, but it does not seem to be the right way to use fallback. Or is it? I am fairly new to Next, so i am a bit confused.
const PostContainer = (props) => {
const [postData, setPostData] = React.useState(null);
const router = useRouter();
React.useEffect(() => {
if (router.query?.id) {
getPost();
}
}, [router.query]);
const getPost = async () => {
const postData = await getPost(`${router.query.id}`);
if (postData) {
setPostData(postData);
}
};
if (router.isFallback) {
return <Post data={postData}
metaProps={defaultMetaProps}
/>
}
return (
<Post data={postData}
metaProps={props}
/>
);
};
export const getStaticPaths = async () => {
const list = await getPosts();
if (list?.length) {
const paths = list.map((post) => {
return {
params: {
id: post.id,
},
};
});
return {
paths,
fallback: true,
};
}
return {
paths: [],
fallback: true,
};
};
export const getStaticProps = async ({ params }) => {
const { id } = params;
const post = await getPost(id);
const {title, description, previewImage} = post
const props = {
props: {
title,
description,
url: `https://sitename./blog/${id}`,
image: previewImage,
},
revalidate: 1,
};
return props
};
export default PostContainer;
And one more thing - do i understand correctly that if fallback option is true, it takes only one visit to the page by ANY user, after which new path is generated on the server permanently and is accessible for anyone who comes next, therefore fallback always fires just once for this first visit of first user?

Upload more images from react-native app to firebase

I'm trying to upload multiple images from react-native to firebase. The way I go about this is by using expo image picker multiple - by monstrodev ( see snack.io here )
I've managed to get it to work, and managed to be able to choose multiple photos from the 'improvised' camera roll. They load fine in the add screen, but I cannot find a solution on how to upload them properly.
export default function Add(props) {
const [name, setName] = useState("");
const [photos, setPhotos] = useState(null);
const uploadImage = async () => {
const uri = photos[0].uri; // -> uri is like this because 'media' is an array with objects inside that contain name, type, and uri each, and I only need uri of each oject.
const childPath = `data/${firebase.auth().currentUser.uid}/${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const upload = firebase
.storage()
.ref()
.child(childPath)
.put(blob);
const uploadProgress = snapshot => {
console.log(`transferred: ${snapshot.bytesTransferred}`)
};
const uploadComplete = () => {
upload.snapshot.ref.getDownloadURL().then((snapshot) =>{
addPost(snapshot);
console.log(snapshot);
})
};
const uploadError = snapshot => {
console.log(snapshot);
};
upload.on("state_changed", uploadProgress, uploadError, uploadComplete );
};
const addPost = (downloadURL) => {
firebase.firestore()
.collection("allPosts")
.collection(firebase.auth().currentUser.uid)
.collection('userPosts')
.add({
downloadURL,
name,
}).then((function () {
props.navigation.popToTop()
}))
}
useEffect(() => {
const {params} = props.route;
if(params) {
const {photos} = params;
if(photos) setPhotos(photos)
delete params.photos
}
}, [{photos}]) // This useEffect updates when coming back from the ImageBrowserScreen (can be found in snack.io, it's exactly the same)
The main problem is, let's say, I choose 3 photos. If I console.log(photos) I get this:
Array [
Object {
"name": "name1.JPG",
"type": "image/jpg",
"uri": "file:///name1.jpg",
},
Object {
"name": "name2.JPG",
"type": "image/jpg",
"uri": "file:///name2.jpg",
},
Object {
"name": "name3.JPG",
"type": "image/jpg",
"uri": "file:///name3.jpg",
},
]
The only I could get it to work was this, give exact path to uri (photos[0].uri for example) otherwise get network error. But this only uploads the first object/photo. I also tried to map through the photos state and return all uri's into a single array and use that as const uri, but that obviously didn't work, for uri needs only one string. So I somehow need to run that function for each uri to be able to get a downloadURL and store each of them.
EDIT:
const uploadImage = async (photo) => {
const uri = photo.uri;
const childPath = `data/${
firebase.auth().currentUser.uid
}/${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const snapshot = await firebase.storage().ref().child(childPath).put(blob);
const downloadURL = await snapshot.ref.getDownloadURL();
imgs.push(downloadURL)
};
const uploadPhotos = async () => {
await Promise.all(photos.map(p=>uploadImage(photo)).then(addPost())
};
Can you try it with a loop trough all photos and upload them separately. Idealy using a Promise.all to upload them in parallel:
const addPost = async (downloadURLs) => {
await firebase.firestore()
.collection("allPosts")
.collection(firebase.auth().currentUser.uid)
.collection('userPosts')
.add({
downloadURLs,
name,
})
props.navigation.popToTop()
}
const uploadImage = async (photo) => {
const uri = photo.uri;
const childPath = `data/${
firebase.auth().currentUser.uid
}/${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const snapshot = await firebase.storage().ref().child(childPath).put(blob);
const downloadURL = await snapshot.ref.getDownloadURL();
return downloadURL
};
const uploadPhotos = async () => {
const downloadURLs=await Promise.all(photos.map(p=>uploadImage(photo))
await addPost(downloadURLs);
};

Dynamic generated sitemap -there are only declared pages on sitemap

I'd like to generate dynamic url fo each slug, but there is an array only with pages which I declared: const pages = ["/", "/about", "/portfolio", "/blog"];
http://localhost:3000/api/my-sitemap. I've installed npm sitemap from https://www.npmjs.com/package/sitemap
my query in ../../lib/data.js
export const getBlogSlugs = async () => {
const endpoint =
"https://api-eu-central-gsagasgasxasasxsaxasxassaster";
const graphQLClient = new GraphQLClient(endpoint);
const query = gql`
{
posts {
slug
}
}
`;
return await graphQLClient.request(query);
};
pages/api/my-sitemap.js
import { getBlogSlugs } from "../../lib/data";
const { SitemapStream, streamToPromise } = require("sitemap");
const { Readable } = require("stream");
export const getStaticProps = async () => {
const { posts } = await getBlogSlugs();
return {
props: {
posts,
},
};
};
export default async (req, res, posts) => {
try {
const links = [];
posts?.map((slug) => {
links.push({
url: `/blog/${slug}`,
changefreq: "daily",
priority: 0.9,
});
});
// Add other pages
const pages = ["/", "/about", "/portfolio", "/blog"];
pages.map((url) => {
links.push({
url,
changefreq: "daily",
priority: 0.9,
});
});
// Create a stream to write to
const stream = new SitemapStream({
hostname: `https://${req.headers.host}`,
});
res.writeHead(200, {
"Content-Type": "application/xml",
});
const xmlString = await streamToPromise(
Readable.from(links).pipe(stream)
).then((data) => data.toString());
res.end(xmlString);
} catch (e) {
console.log(e);
res.send(JSON.stringify(e));
}
};
I added to my robots.txt in pudblic folder:
User-agent: *
Allow: /
Sitemap: http://localhost:3000/api/my-sitemap
What I got is only declared pages
localhost:3000/api/my-sitemap
I tried like this and doesn't work too:
export const getStaticProps = async () => {
const data = await getBlogSlugs();
return {
props: {
posts: data.posts,
},
};
};
export default async (req, res, posts) => {
try {
const links = [];
posts?.map((post) => {
links.push({
url: `/blog/${post.slug}`,
changefreq: "daily",
priority: 0.9,
});
});
You cannot use getStaticProps from an API route.
https://github.com/vercel/next.js/discussions/16068#discussioncomment-703870
You can fetch the data directly inside the API function.
Edit: In my app, I use the API route code below to fetch data from external server
import fetch from "isomorphic-unfetch";
export default async (req, res) => {
try {
const result = await fetch("YOUR_URL");
const posts = await result.json();
//use posts
});
} catch (e) {}
};
For GraphQl may be you can check the example given in vercel site
https://github.com/vercel/next.js/tree/canary/examples/api-routes-graphql

Next.js using SWR with axios

I'm trying to use SWR to prefetch data in my project.
Here is my code:
export const getStaticProps = async (res) => {
const result = await axios.get(
`/orders/detail/${res.params.cid}/${res.params.oid}`
);
const orderDetailById = await result.data;
return {
props: { orderDetailById },
};
};
export const getStaticPaths = async () => {
const result = await fetch(`${server}/api/orders`);
const orders = await result.json();
const ids = orders.map((order_detail) => ({
oid: order_detail.oid,
cid: order_detail.cid,
}));
const paths = ids.map((id) => ({
params: { oid: id.oid.toString(), cid: id.cid.toString() },
}));
return {
paths,
fallback: false,
};
};
const fetcher = (url, params) => {
return fetch(url + params.cid + '/' + params.oid).then((r) => r.json());
};
const OrderDetailByOId = ({ orderDetailById }) => {
const cid = orderDetailById.customer[0].cid;
const oid = orderDetailById.detail[0].oid;
const params = useMemo(() => ({ cid, oid }), [cid, oid]);
const { data, error } = useSWR(['/orders/detail/', params], fetcher, {
initialData: orderDetailById,
});
if (error) {
console.log('errorHere', error);
return <div>failed to load</div>;
}
if (!data) return <div>Loading...</div>;
return <OrderDetailForm orderDetailById={orderDetailById} />;
};
export default OrderDetailByOId;
It works well in the first render.
At the same time, I didn't change any data in my database,
so when it renders the second time by refreshInterval:1000 it wouldn't change anything, but it popped up with some errors!
errorHere SyntaxError: Unexpected token < in JSON at position 0
When I first saw the error I guessed it was just some JSON problems, so I changed the fetcher's return like (r)=>r.data
After I changed this, it caused the web to return loading...
It means it didn't fetch anything in the second render or even each after the first render.
Did anyone can help me find out what problems caused the errors.
Thanks~
I forgot I have set Axios basic URl like Axios.defaults.baseURL = server + '/api';
so I changed the fetcher return like return axios.get(url + params.cid + '/' + params.oid).then((r) => r.data);
It works for me now~ Thanks for the #juliomalves pointing me out where could be a problem ~ Thanks!

How to fetch API data from Axios inside the getServerSideProps function in NextJS?

I'm building an App with Next.js, and I need to connect to specific API routes (set up with API Platform) and populate pages with the route's responses.
The API is working fine, but no matter how I try to implement my Axios call inside the getServerSideProps, I always get the same error, ECONNREFUSED, from my Node stack.
I tried to get the data from useEffect() and it's working fine, but I would like to know if there's a way to call it directly in getServerSideProps.
I'm using a Node container for Docker, and the routes are authenticated through a JWT Token (stored in the session and the client cookies for the server-side connection)
Here are is my code:
pages/accounts.js:
export async function getServerSideProps(context) {
const cookies = new Cookies(context.req.headers.cookie)
const adminToken = cookies.get('jwtToken')
const res = await getAllAccounts(adminToken)
return {
props: {
testdata: ''
},
}
}
lib/accounts.js:
import service from '../service'
export const getAllAccounts = async (adminToken) => {
const res = service({ jwtToken : adminToken }).get(`/accounts`).then((response) => {
}).catch((error) => {
console.dir(error)
})
}
HTTP wrapper:
import axios from 'axios';
import jwt_decode from "jwt-decode";
import mockAdapter from 'axios-mock-adapter';
const service = ({ jwtToken = null, store = null, mockURL = null, mockResponse = null, multipart = false } = {}) => {
const options = {};
options.baseURL = process.env.NEXT_PUBLIC_API_URL + '/api';
if(multipart === true) {
options.headers = {
'Content-Type': 'multipart/form-data'
}
} else {
options.headers = {
'Content-Type': 'application/ld+json',
accept: 'application/ld+json'
}
}
const instance = axios.create(options);
instance.interceptors.response.use(response => {
return response;
}, error => {
return Promise.reject(error);
})
if (mockURL !== null && mockResponse !== null) {
let mock = new mockAdapter(instance);
mock.onAny(mockURL).reply(200, mockResponse)
}
return instance;
};
export default service;
Through the error dump in the node stack, I managed to see that the request headers are correct, and the JWT correctly passed through.
Do not use Axios. Just use fetch().
Next.js polyfills fetch() by default on both the client and server, so you can just use it:
In addition to fetch() on the client-side, Next.js polyfills fetch() in the Node.js environment. You can use fetch() in your server code (such as getStaticProps/getServerSideProps) without using polyfills such as isomorphic-unfetch or node-fetch.
Source.
getServerSideProps works well with axios if we return response.data
export const getServerSideProps: GetStaticProps = async ({ params }) => {
const { brandName } = params as IParams;
const brandData = await $host.get(`api/brand/${brandName}`).then(response => response.data);
return {
props: {
brand: brandData,
},
};
};
Your problem is that your async method does not return a promise.
import service from '../service'
export const getAllAccounts = async (adminToken) => {
const res = service({ jwtToken : adminToken }).get(`/accounts`);
return res;
}
In my NextJS begining I followed this tutorial , and I changed fetch to axios in this way:
export const getStaticPaths = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await res.json();
const paths = data.map((ninja) => {
return {
params: { id: ninja.id.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context) => {
const id = context.params.id;
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
const data = await res.json();
return {
props: { ninja: data },
};
};
I applied the change using useEffect()
useEffect(() => {
// const data = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
// const res = await data.json();
// setninja(res);
const fetchData = async () => {
const result = await axios(`https://jsonplaceholder.typicode.com/users/${id}`);
setninja(result.data);
};
fetchData();
console.log(data);
}, []);
I hope this info will be useful for you.
I Used Axios in getServerSideProps without any problems.
export const getServerSideProps: GetServerSideProps = async({
params,
res
}) => {
try {
const response = await axios.get(`/api/test`);
return {
props: {
data: response.data
},
}
} catch {
res.statusCode = 404;
return {
props: {}
};
}
};

Resources