Hello, I'm a developer from the Wadiz FE Development Team.
Before diving into the main topic, I'll watch a video first.

The video above is from the web, not the app. This time, we applied SPA technology to the main user navigation flow on Wadiz web to improve the user experience. So it's perfectly natural if you don't feel any frustration, inconvenience, or any thoughts at all. In this post, I'll share the story behind that improvement.
Background of Introduction
Many pages Wadizused a hybrid approach combining SSR (Server Side Rendering) using JSP with CSR (Client Side Rendering) using React in certain areas. Visually, it looked like the diagram below.

Displaying the Wadiz page in both JSP and React areas
There were frequent requests to add or modify the legacy JSP pages. Therefore, we chose to replace certain screen areas with React, which is relatively easier to maintain compared to JSP.
However, this method had the following drawbacks:
- The code is distributed across the FE and BE repositories, making it difficult to understand the code flow.
- When communication between JSP and React is required, development becomes difficult.
- All libraries used in JSP and React are loaded, resulting in slow performance.
- Each React section is a separate app, so they call their own APIs and render at different times, leading to frequent CLS (Cumulative Layout Shift) occurrences.
The biggest drawback of hard-to-maintain code is that developers dislike maintaining it. 😂
(When developers dislike maintaining it, the planner's plans hit a major roadblock: the developer's "No way..." response.)
To resolve the aforementioned issues requiring maintenance, we migrated the JSP-based pages to a pure React application and implemented SPA for navigation between pages.
SPA Implementation Process
Converting a page built with JSP + React to pure React
Although we called it a modification, due to the nature of JSP, there was almost no code that could be reused in React. Yes. We developed it from scratch.
We added a REST API to the backend to enable client-side rendering (CSR). We modified the props previously injected in the Java Controller to be fetched via the API. Although it was a new development, the core components were already part of the design system, allowing us to complete it in a short timeframe.
SPA Implementation
새롭게 개발한 페이지를 React Router에 추가하고 페이지 이동을 a tag에서 <Link> 컴포넌트 혹은 history를 사용하도록 변경했어요.
Modifying the bundle (React App) included in JSP
Even when navigating directly to each page, to ensure SPA behavior during page transitions, the same bundle must be included in each page's JSP.
Overcoming the Drawbacks of SPA
Implementing SPA to improve page transition speed wasn't the end of the story. We had to overcome the drawbacks inherent to SPA. The disadvantages of SPA include SEO (Search Engine Optimization), slow initial loading speed, and CLS.
Overcoming Weaknesses 1. SEO (Search Engine Optimization)
Popular search engines support script execution. Therefore, the main issue is providing meta information. Wadizwas using JSP to provide meta information. Even when implementing SPA, they didn't remove the existing JSP files; they only changed the bundle that includes them. Consequently, meta information can still be maintained even after implementing SPA.

Overcoming Weaknesses 2. Slow initial loading
Merging multiple pages split across several 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 slower initial screen loading.
An SPA app doesn't necessarily have to be a single bundle. By splitting bundles using Webpack's SplitChunk feature and loading them lazily with React, you can load only the necessary bundles when needed.
Wadizimproved initial loading speed 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 independently calls and responds to APIs, rendering them separately causes users to experience unexpected LayoutShifts as content jumps around. This phenomenon can be mitigated by rendering or applying a skeleton once all necessary data is ready. We minimized LayoutShift using the following approach.
- Display the spinner until all essential data for page rendering has been received.
- Apply skeleton rendering to elements requiring rendering when content enters the viewport.
- Apply fixed sizes to elements whose content size changes after loading images, etc.
Leveraging the Advantages of SPA
One of the advantages of SPA is that it can also implement behavior similar to a native app. Let me introduce the approach we chose.
Prefetch
The typical SPA page transition operates according to the following flow:
Page transition → API call → Loading bar display → API response → Page rendering
This flow has the issue of always displaying a white screen (loading bar) during page transitions, which holds little meaning for the user. Prefetch can eliminate the loading bar exposure flow because it operates as the next flow.
Card click → API call → API response → Page transition & rendering
We used ReactQuery's staleTime option for data sharing.

To dramatically demonstrate the effect, it operated on a slow network (3G).
History Modal
There's a significant difference in how modals behave between native apps and the web. Specifically, it's how you close the modal. In native apps, you close the modal by clicking the back button. On the web, however, clicking the back button causes you to return to the previous page. You can implement a modal that closes via the back button by using the browser's history API.
When a modal opens, use `history.push` to add it to the history stack. Then, add logic to the `popState` event to close the opened modal. This allows you to implement a modal that closes when the user hits the back button. While this is a simplified explanation, additional technical steps are needed for easier management.

Wadiz Service Video
Keep previous screen
Maintaining the previous screen in the user navigation flow is a crucial feature. In native apps, since pages are newly created upon navigation, preserving the previous screen is a natural behavior. However, on the web, where the same screen is redrawn, maintaining the previous state is challenging.
If the data remains unchanged when returning to the previous screen, you can keep the previous screen intact. In Wadizcase, we manage server data using ReactQuery and apply ReactQuery's staleTime. This allows us to preserve data for a specific duration, ensuring it remains intact even after screen transitions.
One important note is that since ReactRouter restores the scroll position within useLayoutEffect, the data must be restored before this Hook is called. If data restoration occurs too late, separate logic to maintain the scroll position must be added.

Wadiz Service Video
Expanded application to the Wadiz
The Wadiz app is a hybrid app that includes both native components (service home screens) and webviews (detail pages). Previously, whenever a user navigated from a native screen to a detail page, a new webview was created each time. To implement a Single Page Application (SPA), we needed to change this to reuse a single webview, requiring several technical developments. Below are the details of that development.
Develop a page transition function (interface) to enable webview reuse
We needed an interface to request page transitions within the app. Pages that can transition to an SPA are modifiable, and we passed the decision to transition to an SPA as a return value.
Page Transition State Protocol (Event) Definition
In the app, we defined the following events to check success/failure and measure performance during SPA loading.

Developing custom hooks for the component lifecycle to function correctly
On the web, you rarely navigate to the same page repeatedly. However, when reusing a webview in an app, it's common to navigate to specific pages multiple times. Due to React Router's behavior—it reuses components if the path doesn't change—there may be an issue where the initialization code (useEffect) for each component on the page isn't called in the above navigation flow.
When an SPA transition is requested via the interface in the app, we developed a custom hook to clear the current page and redraw it.
Future Plans
We've thoroughly reviewed the optimization work done using SPA so far. Furthermore, I want to address the issue of slower initial page loading compared to the SSR approach. We plan to implement NextJS to improve this by adopting SSR + CSR.
Still have questions? 👀
FE Developer's Guide to Adapting to TDD 👉Click
See How Developers Work 👉Click


