Difference in App behaviour when using pn.serve(...) and running panel serve ... from command line

Hi everyone,

I tried creating a multi page panel app that is hosted inside a single pn.Column(). The structure is as follows:

app.py:

1 import panel as pn
2
3 from src.presentation.navigation.NavState import nav_state
4 from src.presentation.navigation.Screens import screens
5
6
7
8 def get_screen(page_key):
9 print(f"Show Screen {page_key}")
10
11 return screenspage_key
12
13 content = pn.bind(get_screen, nav_state)
14
15 #pn.Column(content).servable()
16 pn.serve(pn.Column(content))

NavState.py:

1 import param
2
3 nav_state = param.rx(“Home”)

Screens.py:

1 from src.presentation.screen.HomeScreen import HomeScreen
2 from src.presentation.screen.OtherScreen import OtherScreen
3
4 screens = {
5 “Home”: HomeScreen,
6 “Other”: OtherScreen
7 }

HomeScreen.py:

1 from panel.viewable import Viewer, Viewable
2 import panel as pn
3
4 from src.presentation.component.TopBar import TopBar
5 from src.presentation.navigation.NavState import nav_state
6
7
8 class HomeScreen(Viewer):
9
10 def init(self, **params):
11 super().init(**params)

12 def navigate(self, event):
13 nav_state.rx.value = “Other”
14
15 def panel(self) → Viewable:
16 return pn.Column(
17 “# Home Screen”,
18 pn.widgets.Button(
19 name=“Go to other screen”,
20 on_click=self.navigate
21 ),
22 sizing_mode=“stretch_both”
23 )

OtherScreen.py:

Looks the same as HomeScreen.py just with a different class name and it sets nav_state.rx.value = “Home” instead of “Other”.

The Problem

When starting the app by running the app.py file

python app.py

The app starts as expected. I can navigate between the two screens, for every time I navigate the print statement (line 9 in app.py) is fired once.

When I now change the app.py from ending with

pn.serve(pn.Column(content))

to

pn.Column(content).servable()

and start the app using

panel serve app.py

the app still works and navigates correctly. However, now the print statement (line 9 in app.py) is fired one additional time for every time I reload the webpage. I want to prevent this, as re-drawing the screens multiple times will negatively impact user experience when the screens get more complex and drawing them takes more time.

The code provided here is a small excerpt and simplification of a larger app we are working on. In this app we are actually experiencing this problem right from the beginning when starting the app using panel serve instead of pn.serve(). Here the screens are drawn twice when first loading the app and then an additional two times for every time the user reloads the web page.

My specific question: It seems I don’t fully understand how the application is served to the user. Can someone please point me to relevant resources and/or explain, why our app behaves this way? Thanks in advance!

The way I think about it is when you run python -m panel serve app.py, then app.py is fully loaded on each refresh of the user session. (see pn.state for more information also)

But, when you use pn.serve you are serving user sessions into panels application/cache state. I prefer to serve up a function to be called on each refresh. Where I declare the class and a function within it that returns the template (parent Panel component) to be used; and then create a function that instantiates the class and call the template function and use that to associate with the route.

There is more information on pn.serve and routes here also, Option #2: Multi page app documentation - #3 by Marc

Hope that is helpful.

Thank you very much for the quick response! I think I understand a bit better now.

So just to recap: Any object that is instantiated outside of app.py (like nav_state = param.rx(“Home”) in the original code) and then simply referenced by app.py, will not be destroyed and recreated. Every object storing data for the app needs to be instantiated from app.py to avoid problems.

Trying to understand your second paragraph - does the following represent the approach you prefer?

SomeScreen.py

SomeScreen(Viewer):
def panel(self):
return pn.Column(…)

App.py

def some_screen():
return SomeScreen()

ROUTES = {
“1”: some_screen
}

pn.serve(ROUTES)

In the documentation on deploying panel apps it says it is preferred to use panel serve app.py instead of pn.serve(). Knowing this, would approach 3 in the link you provided be the best for our use-case (HomeScreen as a starting screen; from it, the user can navigate to many different screens, each allowing navigation to different screens again), since it’s using panel serve and not pn.serve()? I am not a computer scientist, nor am I experienced with hosting web apps or websites, so please forgive my ignorance. Since you, if I understand correctly, prefer pn.serve() and are likely to have experience using it - is there any downside to using it when deploying the app compared to panel serve like the documentation suggests?