TTFB is taking so long (15s-20s) for simple NextJS page in Firebase production - firebase

I have a simple page that is applying SSR as follows:
const page = ({initProps}) => {
// render some static texts
// render images
};
page.getInitialProps = async (ctx) => {
// get id from ctx
// get data from Firestore (get by id, no aggregation)
const firebaseRes = await db.collection("organizations")
.doc(id)
.get();
// return data
}
Currently, in the production environment, it takes around 15s for TTFB.
I tried a lot of things (use next/image, reduce the data amount returned by getInitialProps...) to reduce the latency time but no luck.
Is there anything else I can check/improve for my case?
==========
Add more information:
I run my app as a Firebase function
My page is a landing page (static text, static images, dynamic image loading, one Lottie animation)
I'm using TailwindCSS
My NextJS version is 12.x
Inside the initialProp function, I connect to Firestore directly to get the data.
Inside the initialProp function, besides querying the data, I have a signInWithEmailAndPassword to get the token.

Related

Nuxt 3 useAsyncData access previous value with refresh method

I am trying to implement infinite scroll using nuxt3.
Key target:
Initial load should contain server rendered content
So my code is
const { data, refresh } = await useAsyncData(async () => {
const response = await $fetch(`/api/whatever?limit=${limit}&skip=${skip}`);
return {
totalElements: response.count,
items: response.items,
};
});
Everything is fine here except I couldn't accumulate previous result with current result after refresh() to achieve infinite scroll because data is not accessible inside useAsyncData handler so i can't use
return {
totalElements: response.count,
items: [...data.value.items, ...response.items],
};
Also it is impossible to assign current value to ref and reuse it inside handler as server ref will take changes and client ref will be empty value at initial load.
I have a workaround but it is a kind of a crutch and sometimes results to a glitchy scroll behaviour because server rendered data.value.items is an array and after modifying data.value.items on client side it becomes an array of proxies.
// This code is outside useAsyncData composable
...
const response = await useFetch(...);
data.value.items = [...data.value.items, ...response.items];
Any ideas?

Cypress not stubbing json data in intercept?

I've been searching for a solution all day, googling and StackOverflowing, but nothing appears to be working.
I've got a very simple NextJS app. On page load, I load a fact from a third party API automatically. Then a user can enter a search query, press enter, and search again based on that query. I want to create a Cypress test that checks for the functionality of that search feature.
Right now, I'm getting a timeout on cy.wait(), and it states that No request ever occurred.
app.spec.js
import data from '../fixtures/data';
describe('Test search functionality', () => {
it('renders new fact when search is performed', () => {
// Visit page
cy.visit('/');
// Wait for page to finish loading initial fact
cy.wait(1000);
// Intercept call to API
cy.intercept("GET", `${process.env.NEXT_PUBLIC_API_ENDPOINT}/jokes/search?query=Test`, {
fixture: "data.json",
}).as("fetchFact");
// Type in search input
cy.get('input').type('Test');
// Click on search button
cy.get('.submit-btn').click();
// Wait for the request to be made
cy.wait('#fetchFact').its('response.statusCode').should('eq', 200);
cy.get('p.copy').should('contain', data.result[0].value);
})
});
One thing I've noticed, is that the data being displayed on the page is coming from the actual API response, rather than the json file I'm attempting to stub with. None of React code is written server-side either, this is all client-side.
As you can see, the test is pretty simple, and I feel like I've tried every variation of intercept, changing order of things, etc. What could be causing this timeout? Why isn't the json being stubbed correctly in place of the network request?
And of course, I figure out the issue minutes after posting this question.
I realized that Cypress doesn't like Next's way of handling env variables, and instead needed to create a cypress.env.json. I've updated my test to look like this:
import data from '../fixtures/data';
describe('Test search functionality', () => {
it('renders new fact when search is performed', () => {
// Visit page
cy.visit('/');
// Wait for page to finish loading initial fact
cy.wait(1000);
// Intercept call to API
const url = `${Cypress.env('apiEndpoint')}/jokes/search?query=Test`;
cy.intercept("GET", url, {
fixture: "data",
}).as("fetchFact");
// Type in search input
cy.get('input').type('Test');
// Click on search button
cy.get('.submit-btn').click();
// Wait for the request to be made
cy.wait('#fetchFact').its('response.statusCode').should('eq', 200);
cy.get('p.copy').should('contain', data.result[0].value);
})
});

run function on bases of internet connectivity in react native

I am working on react native application I use firebase as my backend. I fetch data from firebase real time database and render it on the page. But now I want my application to be supported offline.
I used following two functions for rendering.
For listings from database
const loadListings = () => {
let data = [];
listingRef.orderByChild("created_at").on("value", (snapshot) => {
data = [];
snapshot.forEach((listing) => {
data.push(listing.val());
});
cache.store("listings", data.slice(0, 10)); // only stores latest ten listings
setListings(data);
setLoading(false);
});
};
and then use it inside useEffect like.
useEffect(() => {
loadListings();
}, []);
and for listings from cache I used this.
const loadListingsCached = async () => {
let data = await cache.get("listings");
setListings(data);
};
Now I cant put a check inside firs function as effect hook will run only one time and initialy network status is null. its not defined.
how do I achieve this?
by the way link to package I used for detecting connectivity
Edit
I used this hook as second argument to useEffect() but didn't work for me
const netInfo = useNetInfo();
I
What you want to achieve is make the code different depending on what is the network status. In the answer linked by #Rohit there is my answer about how to check the network connectivity with Net Info Package.
What you have to do is make the effect dependant on the status change. You should pass it as a argument to the effect.
const netInfo = useNetInfo();
useEffect(() => {
loadListings();
}, [netInfo]);
This way the code will always run when a network change is detected. I hope this is what you wanted to achive. Please be more specific about you goal and what is the problem. Current questions does not specify if the hook is not working, or the rendering function does not trigger etc.

Been trying to set Custom time on a file using Firebase?

I'm trying to set the Custom time attribute in firebase on the front end. Everything is possible to set, like contentDisposition, custom Metadata etc, just can't find any way or any info about setting Custom time.
You can see it referenced here https://cloud.google.com/storage/docs/metadata#custom-time
You can set the custom time on the file manually in the Storage cloud console, but even when you do and you load the file in firebase on the front end, it's missing from the returned object! (makes me feel like it's not possible to achieve this)
var storage = this.$firebase.app().storage("gs://my-files");
var storage2 = storage.ref().child(this.file);
//// Tried this
var md = {
customTime: now.$firebase.firestore.FieldValue.serverTimestamp()
};
//// & Tried this
var md = {
Custom-Time: now.$firebase.firestore.FieldValue.serverTimestamp()
};
storage2.updateMetadata(md).then((metadata) => {
console.log(metadata);
}).catch((err) => {
console.log(err);
});
The reason I ask is I'm trying to push back the lifecycle delete date (which will be based on the custom time) every time the file is loaded. Does anyone know the answer or an alternative way of doing it?
Thanks in advance
The CustomTime metadata is not possible to update using Firebase JavaScript SDK since it is not included in the file metadata properties list mentioned in the documentation. So even if you specify it as customTime: or Custom-Time: the updateMetadata() method does not perform any changes.
I suggest you as a better practice, set the CustomTime metadata from the cloud console and modify the CustomTimeBefore Lifecycle condition from the back-end each time you load the file using the addLifeCycleRule method of the GCP Node.js Client.
// Imports the Google Cloud client library
const {Storage} = require('#google-cloud/storage');
// Creates a client
const storage = new Storage();
//Imports your Google Cloud Storage bucket
const myBucket = storage.bucket('my_bucket');
//-
// Delete object that has a customTime before 2021-05-25.
//-
myBucket.addLifecycleRule({
action: 'delete',
condition: {
customTimeBefore: new Date('2021-05-25')
}
}, function(err, apiResponse) {});

How to improve preformance of firestore cache query

I am developing a PWA, which displays a list of transactions (transaction is an object with ~10 fields). I am using firestore for storage and realtime updates and I have also enabled persistance.
I want my application to have all the data in memory and I want to take care of displaying only necessary information myself (e.g. using virtual scrolling for transaction list). Due to this reason I listen to the whole collection (a.k.a the transactions).
At the start of the app, I want to make sure the data is loaded so I use one time cache query to get the transactions. I would expect the query to be nearly instantaneous, but on laptop it takes around ~1 second to get the initial data (and I also have another collection which I fetch from cache and this resolves after ~2 seconds after transactions request). For mobile it takes around ~9seconds (loading on mobile, loading on laptop)
I want my app to feel instantaneous, but I takes a few seconds until the data is in place. Note, that I am not doing any advanced queries (I just want to load the data to memory).
Am I doing something wrong? I have read Firestore docs, but I don't think the amount of data that I have in cache should cause such bad performance.
UPDATE: Even if I limit the initial query to just load 20 documents. It still takes around ~2 seconds to retrieve them.
UPDATE 2: The code looks like this:
export const initializeFirestore = (): Thunk => (dispatch) => {
const initialQueries: Array<Promise<unknown>> = []
getQueries().forEach((query) => {
const q = query.createFirestoneQuery()
initialQueries.push(
q
.get({
source: 'cache',
})
.then((snapshot) =>
dispatch(firestoneChangeAction(query, snapshot, true)),
),
)
q.onSnapshot((change) => {
dispatch(firestoneChangeAction(query, change))
})
})
console.log('Now I am just waiting for initial data...')
return Promise.all(initialQueries)
}
You may be interested by the smart approach presented by Firebase engineers during the "Faster web apps with Firebase" Session of the Firebase Summit 2019 (You can watch the video here: https://www.youtube.com/watch?v=DHbVyRLkX4c).
In a nutshell, their idea is to use the Firestore REST API to make the first query to the database (which does not need to download any SDK), and in parallel, dynamically import the Web SDK in order to use it for the subsequent queries.
The github repository is here: https://github.com/hsubox76/fireconf-demo
I paste below the content of the key js file (https://github.com/hsubox76/fireconf-demo/blob/master/src/dynamic.js) for further reference.
import { firebaseConfigDynamic as firebaseConfig } from "./shared/firebase-config";
import { renderPage, logPerformance } from "./shared/helpers";
let firstLoad = false;
// Firestore REST URL for "current" collection.
const COLLECTION_URL =
`https://firestore.googleapis.com/v1/projects/exchange-rates-adcf6/` +
`databases/(default)/documents/current`;
// STEPS
// 1) Fetch REST data
// 2) Render data
// 3) Dynamically import Firebase components
// 4) Subscribe to Firestore
// HTTP GET from Firestore REST endpoint.
fetch(COLLECTION_URL)
.then(res => res.json())
.then(json => {
// Format JSON data into a tabular format.
const stocks = formatJSONStocks(json);
// Measure time between navigation start and now (first data loaded)
performance && performance.measure("initialDataLoadTime");
// Render using initial REST data.
renderPage({
title: "Dynamic Loading (no Firebase loaded)",
tableData: stocks
});
// Import Firebase library.
dynamicFirebaseImport().then(firebase => {
firebase.initializeApp(firebaseConfig);
firebase.performance(); // Use Firebase Performance - 1 line
subscribeToFirestore(firebase);
});
});
/**
* FUNCTIONS
*/
// Dynamically imports firebase/app, firebase/firestore, and firebase/performance.
function dynamicFirebaseImport() {
const appImport = import(
/* webpackChunkName: "firebase-app-dynamic" */
"firebase/app"
);
const firestoreImport = import(
/* webpackChunkName: "firebase-firestore-dynamic" */
"firebase/firestore"
);
const performanceImport = import(
/* webpackChunkName: "firebase-performance-dynamic" */
"firebase/performance"
);
return Promise.all([appImport, firestoreImport, performanceImport]).then(
([dynamicFirebase]) => {
return dynamicFirebase;
}
);
}
// Subscribe to "current" collection with `onSnapshot()`.
function subscribeToFirestore(firebase) {
firebase
.firestore()
.collection(`current`)
.onSnapshot(snap => {
if (!firstLoad) {
// Measure time between navigation start and now (first data loaded)
performance && performance.measure("realtimeDataLoadTime");
// Log to console for internal development
logPerformance();
firstLoad = true;
}
const stocks = formatSDKStocks(snap);
renderPage({
title: "Dynamic Loading (Firebase now loaded)",
tableData: stocks
});
});
}
// Format stock data in JSON format (returned from REST endpoint)
function formatJSONStocks(json) {
const stocks = [];
json.documents.forEach(doc => {
const pathParts = doc.name.split("/");
const symbol = pathParts[pathParts.length - 1];
stocks.push({
symbol,
value: doc.fields.closeValue.doubleValue || 0,
delta: doc.fields.delta.doubleValue || 0,
timestamp: parseInt(doc.fields.timestamp.integerValue)
});
});
return stocks;
}
// Format stock data in Firestore format (returned from `onSnapshot()`)
function formatSDKStocks(snap) {
const stocks = [];
snap.forEach(docSnap => {
if (!docSnap.data()) return;
const symbol = docSnap.id;
const value = docSnap.data().closeValue;
stocks.push({
symbol,
value,
delta: docSnap.data().delta,
timestamp: docSnap.data().timestamp
});
});
return stocks;
}
You're not doing anything wrong. The query will take as much time as it needs to finish. This is why many sites use a loading indicator.
For the first query in your app, it's going to include the time it takes to fully initialize the SDK, which might involve asynchronous work beyond more than just the query itself. Also bear in mind that reading and sorting data from local disk isn't necessarily "fast", and that for larger amounts of documents, the local disk cache read might even be more expensive than the time it would take the fetch the same documents over the network.
Since we don't have any indication of how many documents you have, and how much total data you're trying to transfer, and the code you're using for this, all we can do is guess. But there's really not much you can do to speed up the initial query, other than perhaps limiting the size of the result set.
If you think that what you're experiencing is a bug, then please file a bug report on GitHub.

Resources