AstraDial
cloud leftcloud right
Updating a Production Project Safely by Creating a Development Version
From circuit-switched landlines to packet-switched internet calls — understanding the evolution that transformed business communication.
Muthu
Junior Software Developer, AstraDial
ENGINEERING
When we need to update the UI of a production project, the first instinct is often to open the existing codebase, make the changes, test quickly, and deploy. But in real work, that is one of the easiest ways to create risk. A small visual update can unexpectedly affect layouts, break interactions, disturb data flow, or even interrupt live users if it is done directly on the production version.
In our case, the goal was to update the UI of an existing production project without affecting the live product at all. We wanted complete freedom to redesign the interface, try new layouts, test different components, and move carefully without touching the production server or the production database. That led us to a much safer approach: creating a separate development version of the project and doing all UI work there first.
This blog is based on that real experience. I want to explain not only what we did, but also why this approach was useful, how the development environment was prepared, how we handled authentication and data without using the real backend, and how this helped us redesign the UI in a practical and safe way.
Why we created a separate development version
The main reason was simple: we did not want the production product to be disturbed while updating the UI.
UI changes may look harmless from the outside, but in a real project they often touch many connected parts. A new layout may require different props, a component restructure may affect navigation, table rendering can change, forms can behave differently, and styles can conflict with existing logic. When all of this happens directly inside the production environment, the chances of creating unwanted side effects become high.
So instead of working on the live version, we prepared a development version of the project. That allowed us to work freely, redesign screens, replace components, test flows, and even temporarily bypass some production-only logic, all without putting the real users or real data at risk.
Another important decision was that we did not connect the development version to the production database or production server, because our work was focused only on the UI. For UI development, what matters most is that pages render correctly, components receive the expected data shape, and interactions behave properly. Real business data is not required in the early stage if the application can be filled with proper test data.
That is where TypeScript became a very big advantage. Since the project already had typed structures, every component already knew what kind of data it expected. This made it easier to create mock data for each page and keep the UI meaningful during development.
How the development setup was planned
The setup was done in a way that separated local work, repository flow, and VPS usage.
We used Claude to help prepare the development version setup and structure the workflow. However, Claude could not access the VPS because it did not have the VPS credentials. So the repository preparation work such as forking, cloning, and branch setup was done locally. After that, the workflow became simple: whenever we made changes locally, we pushed them to the forked repository branch, and then pulled those changes into the VPS environment whenever we wanted to test there.
This approach gave us two useful benefits. First, the code editing and Git operations stayed under our control. Second, the VPS remained only as a testing and running environment, not the main place where we write and manage all changes.
VPS preparation
Before starting the actual UI work, the server environment had to be checked properly. There is no use in preparing a clean development branch if the environment where it runs is incomplete.
The VPS used for this work was:
163.235.225.238 // EXAMPLE VPS IP
The first step was to verify that the required tools were already installed. We checked Node.js, npm, Git, Nginx, and Certbot. Since the project needed a process manager, PM2 was installed as well.
A typical verification process looked like this:
node -v
npm -v
git --version
nginx -v
certbot --version
pm2 -v
If PM2 is not installed, it can be installed with:
npm install -g pm2
This stage may look basic, but it is important. A missing tool or wrong version often creates confusion later, especially when the application runs differently on local and server environments.
Repository setup for safe UI development
To avoid directly working on the original repository, we first created a fork. The original repository was forked into our GitHub account, and then the fork was cloned.
The structure was like this:
Original repository: organization_name/repo_name

Forked repository: user_name/repo_name
A safe Git workflow started from here.
Step 1: Fork the original repository
This was done on GitHub so that our changes would remain isolated from the original project until we were ready.
Step 2: Clone the fork locally
git clone https://github.com/user_name/repo_name.git
cd repo_name
Step 3: Create a development branch from main
git checkout main
git checkout -b dev
git push -u origin dev
This dev branch became the working branch for all UI-related changes.
Step 4: Add the original repository as upstream
git remote add upstream https://github.com/organization_name/repo_name.git
git remote -v
This is useful when we want to sync changes from the original repository later without mixing them directly into our working branch.
Why we did not touch the production server or production database
This was one of the most important decisions in the whole process.
Our goal was to update the UI, not change live business logic or production data. If we had connected the development version directly to the real database and real services from the beginning, then even small mistakes could have affected actual data, login flow, or other user-facing features.
Since the UI needs data mainly for rendering and interaction testing, we decided to use mock data instead of real backend connections in the development version. This made the development process faster, safer, and easier to control.
It also gave us freedom to work even when the backend was unavailable, incomplete, or not needed for a particular page.
Creating a development mode that can run without the real backend
To make the UI work independently, we introduced a development mode bypass. This was done through a few focused changes on the dev branch.
The idea was not to permanently remove authentication or backend logic. The idea was only to let the app run safely in development mode without requiring real production services.
1. Firebase bypass
The app was updated so that Firebase initialization would be skipped when the API key was only a placeholder. One practical check used was whether the key started with the real Firebase key prefix pattern or not.
const firebaseApiKey = process.env.NEXT_PUBLIC_FIREBASE_API_KEY;

const isRealFirebaseKey =
  firebaseApiKey && firebaseApiKey.startsWith("AIza");

if (isRealFirebaseKey) {
  // initialize Firebase
} else {
  console.log("Skipping Firebase initialization in development mode");
}
This kept the production behavior unchanged when real keys are present, while allowing the development version to run safely when placeholder values are used.
2. Authentication bypass
Since we were only updating the UI, we did not need the full login flow during early development. So in development mode, the app automatically authenticated as an admin user and skipped the login page.
const isDevMode = process.env.NEXT_PUBLIC_DEV_MODE === "true";

if (isDevMode) {
const mockAdminUser = {
id: "dev-admin",
name: "Development Admin",
role: "admin",
email: "[email protected]",
};

// set mock user into auth state, if required
This gave us direct access to the full application layout and pages without depending on Firebase auth during UI work.
3. Mock data layer
This was one of the most useful parts of the setup.
A dedicated file such as the following was created:
lib/mock/data.ts
Inside it, realistic sample data was added for all the main API clients used by the project, such as gateway, PBX, Firestore, workflow, and msg91. The purpose was not just to fill the screen randomly, but to provide structured test data that matched the types expected by the components.
A simple example of mock data structure could look like this:
export const mockUsers = [
{
id: "u1",
name: "John Admin",
email: "[email protected]",
role: "admin",
status: "active",
},
{
id: "u2",
name: "Sarah Manager",
email: "[email protected]",
role: "manager",
status: "active",
},
];
Because the project used TypeScript, components could work with this test data more reliably. The types helped ensure that the mock data followed the same structure expected by the real application. That made the development version much more useful than a blank or broken interface.
As a result, all pages could render properly, and the application behaved enough like the real product for UI redesign work to continue smoothly.
Local development workflow
Even though the application would be tested on the VPS, the main code editing workflow stayed on the local machine. The project was also cloned locally at:
C:\Users\muthu\Desktop\repo_name
1. The working pattern became simple:
2. Make changes locally
3. Commit the changes to the dev branch
4. Push to the forked repository
5. Pull the changes on the VPS
6. Run and verify the development version there
Typical commands looked like this:
git add .
git commit -m "Update dashboard UI layout"
git push origin dev
Then on the VPS:
cd /var/www/repo_name-dev
git pull origin dev
npm install
npm run build
pm2 restart repo_name-dev
This workflow kept development organized and prevented the VPS from becoming the main editing environment.
Understanding the existing UI before changing it
Before redesigning anything, we spent time understanding the existing product UI. This is a step many people skip, but it matters a lot.
A production product already has its own structure, spacing system, layout logic, repeated components, and user flow. If we redesign without understanding that, the new UI may look modern in isolated screenshots but feel disconnected when used as a full product.
So before updating pages, we studied the existing interface carefully. We looked at:
1. how the layout was structured
2. which components were reused
3. how spacing and typography were handled
4. how tables, forms, cards, and navigation behaved
5. what kind of visual language the old UI already had
At the same time, we also studied our reference UI system. In this case, we planned to use shadcn/ui blocks for redesigning parts of the application. Since components.json already existed, shadcn/ui was partially set up, which made it easier to integrate blocks and new components into the existing project.
This step was important because a good UI update is not just copying a beautiful design from elsewhere. It is about understanding both the current product and the target design direction, then combining them properly.
Updating the UI in the development version
Once the development setup was stable and pages could render using mock data, the real UI work started.
The main goal was to redesign and improve the interface using a cleaner component approach. This included areas such as:
1. dashboard layout
2. sidebar navigation
3. login page
4. data tables
5. cards and content blocks
6. page-level spacing and alignment
Since we were working in a safe development version, we could update each page gradually. We did not need to rush or fear affecting the live product.
A typical process for each screen looked like this:
Step 1: Open the existing page and understand its structure
We first checked how the current page was built, what components it used, and what data it expected.
Step 2: Match the page against the reference UI
We compared it with the target UI style or the shadcn block we wanted to use.
Step 3: Replace layout and visual structure carefully
Instead of changing everything blindly, we updated container structure, spacing, alignment, sections, and reusable elements one by one.
Step 4: Reconnect the page to the same typed data
Even though the visuals were changing, the data shape remained the same through TypeScript and mock data. This helped avoid unnecessary logic breakage.
Step 5: Check responsiveness and interaction
After the redesign, we tested how the page behaved on different widths and whether buttons, tables, navigation, and forms still made sense visually and functionally.
This process repeated for each page until the development version started feeling like a polished new interface rather than a collection of disconnected updated screens.
Why test data was enough for the early UI phase
Using mock data was not just a workaround. It was actually the right tool for this phase.
For UI work, the main questions are:
1. Does the page render correctly?
2. Does the layout work with actual-looking content?
3. Do tables, forms, badges, buttons, and cards align properly?
4. Does the component handle empty, filled, and edge-case states?
5. Does the user flow feel clear?
All of this can be tested using realistic mock data if the data shape matches the real one. Since TypeScript already defined the expected structure, creating useful test data became much easier.
This allowed us to move fast in the design phase without waiting for real backend access or worrying about production safety.
Testing after the UI update
Once the UI changes were completed, the next step was not deployment. The next step was full testing inside the development version.
This is another part that should never be skipped. A UI may look correct in screenshots but still have many hidden issues when used like a real product.
The first round of testing was done using the same mock data environment. This allowed us to verify the UI in a stable and predictable state.
We checked:
1. all updated pages
2. navigation between pages
3. button placements
4. table rendering
5. card layouts
6. typography and spacing consistency
7. responsive behavior
8. empty and filled states
9. visual issues such as overflow, broken alignment, and inconsistent colors
After that, the next stage was to test with the real server and database, but only after the UI was already stable. This is important. If we connect real services too early, debugging becomes harder because UI issues and backend issues mix together.
When connecting to real data, the goal is to verify whether the updated UI still behaves properly with actual values and live conditions. In that phase, testing should include:
1. whether all components render correctly with real records
2. whether data length variations break layouts
3. whether permissions and user states affect the interface correctly
4. whether existing features still work as expected with the new UI
5. whether styling holds up under real content, not just sample content
This is where many final issues appear, especially with long text, missing fields, variable counts, and live status values. So this stage is essential before considering production deployment.
Moving from development to production
Only after all testing is complete and the development version is stable should the work move toward production.
By this stage, the development version has already served its purpose well. It allowed us to redesign the UI safely, test step by step, and identify problems early without affecting the live product.
The production move should happen only when:
1. the new UI is complete
2.all major flows are tested
3. mock-data testing is successful
4. real-data testing is successful
5. no serious visual or functional bugs remain
Then the final code can be reviewed, pushed properly, and merged from dev to main through the normal Git workflow.
A typical merge flow may look like this:
git checkout main
git pull upstream main
git checkout dev
git pull origin dev
git checkout main
git merge dev
git push origin main
In real team workflows, this is usually done through a pull request rather than a direct merge, so that the changes can be reviewed before release.
What we can take from this:
Working this way made one thing very clear, updating a production project is not just about writing better UI code. It is about creating a safe process around the work.
By building a separate development version, we got a controlled environment where we could experiment, redesign, and test without fear. By avoiding the production server and database at the beginning, we reduced risk. By using TypeScript and realistic mock data, we kept the UI development meaningful. By delaying real-data integration until later testing, we kept the debugging process cleaner.
Most importantly, this approach let us work with confidence. We were not guessing whether a UI change would affect live users. We knew it would not, because the work was isolated properly from the start.
That is probably the biggest advantage of this method. It gives the developer freedom to improve the product while still respecting the safety of the production environment.
Conclusion
Updating the UI of a live project can feel risky, especially when the product is already in use. But that risk becomes much easier to manage when the work is done through a separate development version.
In our case, the full process included preparing the VPS, setting up a safe Git workflow through a fork and a development branch, bypassing production-only services for UI work, using TypeScript-based mock data to keep all pages usable, studying the existing product before redesigning it, updating the UI screen by screen, testing everything first with test data and then with real data, and only then preparing for production deployment.
This experience showed that a good UI update is not only about making the product look better. It is also about building the right workflow around the update so that the product stays safe while development moves forward.
When done properly, a development version is not just a copy of the project. It becomes a safe workspace where improvement can happen without damaging what already works.
Share on social media
You might also like