Hello, I’m a developer on the Wadiz FE development team.
Before I get into the main topic, I’d like to share a video with you.

What do you think? If you don’t feel anything, that’s exactly right!
The video above is from the web version, not the app. We’ve recently improved the user experience on the web by applying SPA technology to the main navigation paths Wadiz. So, it’s perfectly natural that you don’t feel any lag, discomfort, or anything else—it just feels seamless. In this post, I’ll walk you through the details of these improvements.
Background
Many Wadizpages used a mix of SSR (Server-Side Rendering) with JSP and CSR (Client-Side Rendering) with React in certain sections. Visually, it looked like this:

A view of the Wadiz page divided into JSP and React sections
We frequently received requests to add or modify features on our legacy JSP pages. So, we decided to use React—which is relatively easier to maintain than JSP—to update certain sections of the interface.
However, this method had the following drawbacks:
- A single page of code is scattered across the front-end and back-end repositories, making it difficult to follow the code flow
- When communication between JSP and React is required, development becomes difficult
- All libraries used in JSP and React are loaded, causing slow performance.
- Since each React section is implemented as a separate app, they call the API individually, resulting in different rendering timings and causing frequent CLS (Cumulative Layout Shift) issues.
The biggest drawback of code that’s hard to maintain is that developers don’t want to maintain it. 😂
(If developers don’t want to maintain the code, the planner’s design will run into a major roadblock in the form of the developer’s “No way…”.)
To address the maintenance issues mentioned above, we converted the pages, which were originally built with JSP, into a pure React app and implemented a single-page application (SPA) for navigation between pages.
The SPA Implementation Process
Convert a page built with JSP and React to pure React
Although I said it was a modification, due to the nature of JSP, there’s hardly any code that can be reused in React. Yes, I developed it from scratch.
We added a REST API to the backend to enable client-side rendering (CSR). We also modified the code so that the props previously injected in the Java controller are now fetched via the API. Although this was a new development project, the key components were already part of our design system, which allowed us to complete it quickly.
SPA Implementation
새롭게 개발한 페이지를 React Router에 추가하고 페이지 이동을 a tag에서 <Link> 컴포넌트 혹은 history를 사용하도록 변경했어요.
Modifying the bundle (React App) included in JSP
Even if you navigate directly to each page, you must include the same bundle in the JSP for each page so that it functions as an SPA when switching pages.
Overcoming the Drawbacks of SPA
It wasn’t enough to simply improve page transition speeds by implementing an SPA. We also had to overcome the drawbacks of SPAs. These drawbacks include SEO (Search Engine Optimization), slow initial loading speeds, and CLS.
Overcoming Weaknesses 1. SEO (Search Engine Optimization)
Popular search engines support script execution. As a result, the main concern is typically the provision of metadata. Wadizwas using JSP to provide metadata. Even after implementing a single-page application (SPA), we did not remove the existing JSP files; we simply updated the bundles being included. This allowed us to retain the metadata even after implementing the SPA.

Overcoming Drawbacks 2. Slow Initial Loading
Merging pages that were previously split into multiple apps (bundles) into a single app (bundle) increases the bundle size. While SPA apps offer the advantage of fast screen transitions, they have the disadvantage of slow initial page loading.
Just because it’s called an SPA app doesn’t mean it has to be a single bundle. By splitting the bundles using Webpack’s SplitChunk feature and loading them with React’s `lazy` hook, you can load only the necessary bundles when they’re needed.
Wadizimproved initial loading speeds by separating each page and large third-party libraries into separate bundles and implementing lazy loading.
Overcoming Weaknesses 3. CLS
When each area of the screen calls and responds to the API independently and renders separately, users experience unexpected layout shifts where the screen content shifts unexpectedly. This issue can be resolved by rendering the screen only after all necessary data is ready or by applying a skeleton. We minimized layout shifts using the following methods.
- Display the spinner until all required data for page rendering has been received
- Apply a skeleton to elements that require rendering when they enter the viewport
- Apply fixed dimensions to elements whose size changes after loading, such as images
Leveraging the Benefits of SPA
One of the advantages of SPA is that it can replicate the behavior of a native app. Let me explain the approach we chose.
Prefetch
A typical SPA page transition follows this flow:
Page transition → API call → Load bar displayed → API response → Page rendering
This flow has the issue that a white screen (loading bar) is always displayed during page transitions, which is of little significance to the user. Since Prefetch operates as the next flow, it can eliminate the flow that displays the loading bar.
Click card → API call → API response → Page transition & rendering
We used the `staleTime` option in ReactQuery for data sharing.

To demonstrate the effect dramatically, I ran the test on a slow network (3G).
History Modal
There is a significant difference between how modals behave in native apps and on the web—specifically, how they are closed. In native apps, modals are closed by clicking the back button. On the web, however, clicking the back button typically takes you back to the previous page. By utilizing the browser’s History API, you can implement a modal that closes when the back button is clicked.
You can implement a modal that closes when you click the "Back" button by using `history.push` to add entries to the history when the modal opens, and adding logic to close the modal when the `popState` event is triggered. While this is a simple explanation, a few additional technical steps are required to manage this effectively.

Wadiz Service Video
Keep the previous screen
Preserving the previous screen in the user navigation flow is a very important feature. In native apps, since pages are created anew when navigating between them, preserving the previous screen is the expected behavior. However, on the web, since the content is redrawn within the same screen, preserving the previous screen can be challenging.
If the data remains unchanged when you return to the previous screen, you can keep the previous screen's state. In the case Wadiz, we manage server data using ReactQuery and apply ReactQuery's `staleTime` property. This allows us to retain the data for a specific period, ensuring it remains intact even after a screen transition.
One important point to note is that since React Router restores the scroll position using `useLayoutEffect`, the data must be restored before this hook is called. If the data restoration occurs too late, you’ll need to add separate logic to maintain the scroll position.

Wadiz Service Video
Expanded to the Wadiz app
The Wadiz app is a hybrid app that combines native components (service home screens) and WebViews (detail pages). Previously, whenever a user navigated to a detail page from a native screen, a new WebView was created each time. To implement a Single-Page Application (SPA), we needed to switch to a system that reuses a single WebView, which required some technical development work. Below is a summary of that development process.
Develop a page transition interface to enable the reuse of the WebView
I needed an interface to request page transitions within the app. The pages that can be converted to a SPA are configurable, so I passed the decision to convert to a SPA as a return value.
Definition of the page transition state protocol (event)
In the app, we have defined the following events to check for success or failure and measure performance when the SPA loads.

Developing a custom hook to ensure the component lifecycle functions properly
On the web, users rarely revisit the same page repeatedly. However, when reusing a WebView in an app, users often revisit specific pages repeatedly. Because React Router reuses components as long as the path remains unchanged, this can lead to an issue where the initialization code (useEffect) for each component on the page isn’t triggered in the flow described above.
We developed a custom hook to clear the current page and redraw it when the app requests a SPA transition via the interface.
Future Plans
We’ve taken a close look at the optimization work we’ve done using SPA so far. Moving forward, I’d also like to address the issue of slower initial page loading compared to the SSR approach. I plan to implement Next.js to improve the site using a combination of SSR and CSR.
Do you still have any questions? 👀
How FE Developers Adapt to TDD 👉Click
See how developers work 👉Click


