Page refresh is slow and ugly

I wrote a multipage app where each page hosts a number of components which read data via API from an independent backend server.

The problem is when another page is requested, there are a few seconds when the old page begins to vanish (but not completely: it assumes a whitish color), and then, one by one, Streamlit components appear almost in random order to populate the new page.

The final effect is ugly to see, and the whole app looks very unprofessional.

Is there any way to apply a ā€œloading blanketā€ (it could be a full screen image) to hide immediately the old page and that will be removed as soon as the new page has been fully prepared in background?

Or is it possible to modify st.Spinner using CSS to hide the whole page till it is created?

As an alternative, is there any JS event to know that the page has been completely created in Frontend, and its components are alive and kicking?

Hi @Nemecsek

I have created a simple example app where you can position all the page elements to run after the time element (you can replace this with a lengthy calculation). Thus, the page elements will only be displayed only after the lengthy calculation is complete (in this case the time.sleep(10)).

Hereā€™s the code:

import time
import streamlit as st

with st.spinner('Wait for it...'):
    time.sleep(10)
    st.title('Test app')
    st.write('1')
    st.write('2')
    st.success('Done!')

And the screencast is shown below:
ezgif.com-video-to-gif (1)

Hope this helps!

Best regards,
Chanin

Hi.
Alas I did a very similar test, but it doesnā€™t work in my case.

Just to show what I mean, I made an animated GIF to show the effect of clearing and adding all components again and again at every page change (with streamlit_option_menu), but it is too big to be seen here. Here is an external link.

The speed I very slow because I choose for this example a few components that are intrinsecally slow to refresh (each is a P5js sketch embbeded in the page, not a proper React component).
In any case, this slowness in rendering the page is visible also when there are a lot of standard Streamlit components.

So my question has to do with a procedural trick I would like to implement:

  1. overlay a nice image full screen with an hourglass, a nice kitty or something similar just to distract the user for a few seconds;
  2. clear the previous page content;
  3. populate the page of the necessary components according to the active page, even if this takes a few seconds;
  4. remove the image and make the real page appear in its splendor.

I vote for the kitty! :black_cat:


Have you tried using st.empty? By your description, I am assuming you have a single script providing your pages instead of using the built-in multipage functionality. The ghosting of widgets is something Streamlit does while waiting to see if it those widgets are going to be kept or discarded. If you have each page in a container within an empty element, you could have a means to tell Streamlit to just throw everything away and not wait.

import streamlit as st
import time

page_selection = st.selectbox('Page', ['A','B','C'])
page_container = st.empty()

def content(page):
    page_container.empty()
    time.sleep(.2) # A hack to make the empty stick.
    body = page_container.container()
    with body:
        with st.spinner():
            time.sleep(5)
        st.write(page)

content(page_selection)

I must add my app is quite big, a lot more than any simple demo I found in the wild. It has something like 20 pages and around 150 plots, some of them to update in real time.

Yes, I tried with st.empty and it is even worse :slightly_frowning_face:
No way a kitty can help me.

With st.empty, when for any reason my server is slow to return the page sometimes the frontend doesnā€™t wait for the whole content to be loaded and just visualize whatā€™s available, neglecting what comes later.
That means sometimes the streamlit_option_menu is missing (it looks like extra components are injected AFTER the standard components in Streamlit), sometimes the content of the previous page is grayed out and never removed, or there are other artifacts. Adding time.sleep(0.1) after playing with st.empty doesnā€™t help.

I tried tons of alternative solutions, and the last one would be to hide all during the load, letting all the bad stuff happen in background, and then, tadah!, show the whole page when it is ready in its full glory.

I assume there are issues due to desynchronization between frontend and backend (like the delay associated to st.session_state)

Hi @Nemecsek , I have the same problem.

Maybe I was naive in what Streamlit is, but I am worried that Sreamlit is more limited that I first assumed - it doesnā€™t seem to be able to gracefully handle even quite simple backend logical flow. Their demo gallery contains trivial examples of sites - where have all the sophisticated demos gone? Like that astro imaging one?

In any case, I seem to be butting against the tech limitations of Streamlit and am terrified this is a rewrite in something like Dash or even full JS. Neither of which I have experience of :slightly_frowning_face:

Anyway, Iā€™d be interested to know if you come across optimisations that have helped you :slight_smile:

hey @altanner :wave:

I donā€™t want to flex anything, but I built and released a quite big and complex Streamlit-only app!

This is the app: https://admin.ecoapp.page (login with test@ecoapp.page and test as password)

I know itā€™s not real-time, but every page query big databases multi time

2 Likes

You are allowed to flex, that does look quite impressive. But that is Flask flex, not Streamlit flex, right?

Iā€™m not entirely sure why you used Streamlit at all!

The app is Streamlit only :smile:

At this point, I also donā€™t know why I started using Streamlit at the beginningā€¦
BUT, right now I canā€™t regret using it, because I love the simplicity of this framework!

At the start I struggle with a lot of things, but after some times and more knowledge of how Streamlit works I can said that almost everything could be done

I also know that Iā€™m probably the only one in this situation, so :joy:

Alas it doesnā€™t work like this.
st.spinner cannot be used for a container, in my case a whole page.

You see it here, in a simulation of what happens in real life:

import time
import streamlit as st

with st.spinner('Wait for it...'):
	time.sleep(3)
	st.title('Test app')
	time.sleep(3)
	st.write('1')
	time.sleep(3)
	st.write('2')
	time.sleep(3)
	st.success('Done!')

If it was linked to a container, it would show all the components in a single shot at the end of the last delay. As it is, instead, shows the various parts of the page as soon as they are ready. It is the thing I would like to avoid.

Thatā€™s why I am searching a way to ā€œhideā€ the page while it is building, then show it at the end, when all has been prepared behind the curtains.

Hi !

I would love to have a look at your app for inspiration. The test login sadly does not seem to work anymore.
Would it be maybe possible to reenable it?

Best regards and many thanks
Fabian!

Hey @Tian :wave:

Someone changed the password (I think I need to disable password change in test account ahah)

Now, it should works!

2 Likes

Hey @Mortafix :slight_smile:

Thank you very much!

The site looks really impressive. Especially the fast loading times are great. You also managed to keep flickering of widgets to a minimum. Respect!

Best regards
Fabian

1 Like

I must be misunderstanding you, because in my tests I found the opposite. I could use st.spinner for a container, but the elements in the container were shown as they were ready, not in one single shot.

Another example of what I mean using a mock up page that is functionally very similar to the real one I am struggling with:

import streamlit as st
import time

with st.spinner("Wait..."):
	st.button("Button1")
	st.write("These components should appear only when the page is complete!")
	time.sleep(5)  # in real life here we are reading a remote DB
	st.line_chart([1,2,4,3,5])  

I need to calculate the whole page, and only when completed make it appear.
In the meantime there should be only the spinner.

As it works now, I see immediately [Button1] and the text, then the rest appears at the end of the elapsed time.

In this case that is easy to fix by doing the slow operation before showing the UI elements.

import streamlit as st
import time

def get_data():
    time.sleep(5)
    return [1, 2, 4, 3, 5]

with st.spinner("Wait..."):
    data = get_data()

st.button("Button1")
st.write("These components should appear only when the page is complete!")
st.line_chart(data)
3 Likes

Hey,
I see it is hosted on through Akamai cdn. The app is greate and loads really fast and instantly. Could just write in short how you got it working with a cdn? and how you got it to load so fast.

@ab26 I found a useful guide you can use

My app is deployed on Linode in the same way :balloon:

I agree with @Goyo. Having a clean separation between data loading and data rendering/visualization is definitely the way to go. Then you can more easily incorporate performance optimizations in the data tier. For example, API calls can be streamed, file-based data loads can be cached or replaced with a lightweight SQLite database (e.g. :memory:), or you can incorporate functools.lru_cache and diskcache (instead of st.data_cache).

Non developers and those new to Streamlit should be aware (in my opinion) that there is often a point in your appā€™s development when a bit of ā€œarchitecture refactoringā€ is required. This is especially true when you build a monolith Streamlit application with lots of functionality and doing heavy data workloads. Using st.session_state and st.data_cache can get you a long way. But, when you need more performance and responsiveness, then you must look elsewhere for solutions. I do a bit of performance profiling to find my appā€™s hot spots and then off load them through some simple refactoring and design changes. I find this almost always boils down to optimizing how I sling data around in my app including but not limited to how data is formatted.

P.S. Everyone needs to keep this diagram in mind when building data apps (courtesy of bytebytego.com)

1 Like