CDEK.Shopping: 37% fewer regression bugs


- Role
- Middle Frontend Developer
- Team
- 4 frontend, 8 backend, 2 QA and a designer
- Timeline
- 2 years, 10.2021 – 10.2023
- Status
- The site is live, my work on the project is over
What I did
- Customer accountThe entire frontend of the account in Vue 2 and Vuex: five sections in one app with no page reloads. I wrote every component of the account from the team designer's mockups.
- Vue inside BladeConvinced the team to adopt Vue 2 and came up with a way to embed the SPA account into Laravel + Blade without a separate frontend project.
- Legacy refactoringCovered the jQuery code with Cypress E2E tests and visual baselines, then split files of 6,000+ lines into modules with Jest unit tests.
- Laravel and analyticsBlade templates, routes and account data in API responses. Set up Yandex Metrica and added events. Reviewed the tasks of the other frontend developers.
Context
Business
CDEK.Shopping is an agent for shopping abroad run by CDEK.Market LLC, a service of the CDEK group. Customers in Russia order products of brands from the US, Europe and Asia: they pay with a Russian card and see prices in rubles right away.
The service handles customs clearance and logistics, and orders are delivered by the group's own logistics company.
If the product is not in the catalog, the customer leaves a purchase request: sends a link to the product in a foreign store and specifies the size, color and quantity. A manager checks availability and calculates the final price in rubles with delivery and the service fee. After confirmation and payment, the service buys the product, and the order appears in the account on the “By purchase request” tab.
The situation
The business wanted to launch as soon as possible, so the new service was built on the CDEK Market codebase: jQuery and Blade, files of 6,000+ lines. The customer account there was built on server templates with no interactivity: every action reloaded the page, and the customer waited for the server at every step.
The accumulated tech debt slowed development down and was waiting to be solved, but the plan had no separate time for refactoring: it had to run alongside the launch, on the same deadlines.
Goal
Build an interactive customer account by the launch, where no action reloads the page. CDEK.Shopping users never saw the old account: they started right away in the new one.
Process, architecture and decisions
Customer account
The account is built as one Vue app: each section is a separate module with its own Vuex state and API requests. So the customer moves between sections without a page reload, and a new section can be added without touching the others.
- 01My detailsForms for contacts and customs clearance details, with the email and phone change moved into a separate flow
- 02Orders“All”, “Unpaid” and “By purchase request” tabs, a filter and pagination with no reload, the status of every item, a separate order page
- 03My pointsActive, accrued and frozen points from the loyalty program API, the points history
- 04Delivery addressChoosing the default address, switching between delivery to the door and to a pickup point
- 05ReviewsProducts waiting for a review and reviews already written, in one section
- 06NavigationCounters of unpaid orders and products without a review on the tabs, hash routing, a separate mobile navigation
Architecture
The storefront stayed server-rendered, while the account runs as a single-page app inside the same site. It did not need a separate frontend project.
- STOREFRONTStorefront
Server rendering with Blade, interactivity with jQuery.
- ACCOUNTCustomer account
Vue 2 and Vuex. The backend serves an #app container, the app mounts inside it, routing goes through the hash.
accountorderListingorderDetailsPage
- APIREST on Laravel
The account gets its data through axios: profile, orders, points, delivery and reviews.
/api/v1/account/api/v1/loyalty/api/v1/customer
- SERVICESExternal services
Payments, the Mindbox marketing tracker, Diginetica search, Yandex Metrica.
Account components
We designed the component system together with the team designer, with BEM naming. The account has 16 base UI components: buttons, a checkbox, tabs, a select, a filter, a modal, a notification, a tooltip, pagination and others.
They make up about 40 section blocks, and the icons live in a sprite of about 30. I built the account responsive from the start, with a separate mobile navigation.
Integrations
- Yandex Metrica · events
- Online payments
- Mindbox · marketing tracker
- Diginetica · search with autocomplete
connected by me
Data security
The account works with customers' personal data, including passport details for customs clearance. They are stored encrypted in the database, and the profile API responses return them only masked. To see the data in full, the account makes a separate request to a protected endpoint. The server checks the user's session and permissions, decrypts the data and returns it only in that response. On the client it is not stored in Vuex or cached: once the block is closed, the mask is shown again.
Quality and releases
Before the refactoring I covered the store's critical scenarios with Cypress E2E tests and took visual baselines of the components. The modules that appeared after the refactoring are covered with Jest unit tests.
Every task goes through CI checks and a test stand, then on the planned release date it lands on a preprod identical to production. After acceptance the release rolls out to production with no downtime, and if something goes wrong we can quickly roll back to the previous stable version.
- develop test stand
- preprod acceptance
- master production, no downtime
We worked in two-week sprints with a demo at the end of each. Every task went through code review, and I reviewed the tasks of the other frontend developers.
Challenges
Legacy files of 6,000+ lines
- Problem
- The jQuery client code lived in files of 6,000+ lines. Writing unit tests for such spaghetti was impossible, and touching it without tests was risky.
- Solution
- First I covered the critical scenarios with Cypress E2E tests and took visual baselines, so the refactoring would break neither the logic nor the layout. Then, following the Refactoring.Guru principles, I moved repeated code into modules and kept splitting them until only pure functions covered with Jest unit tests were left.
- Result
- Regression bugs dropped by 37% according to QA reports. Features started shipping faster, and sprint overruns went down.
Vue 2 in a jQuery project
- Problem
- The whole project was written in jQuery and Blade. An interactive account needed a modern framework, and the team had to be convinced to adopt one.
- Solution
- Got approval to add Vue 2 to the account and embedded it without a separate project: the backend serves an #app container, the app mounts inside it, routing goes through the hash. The account was meant to show on a live example that writing in Vue is simpler and faster than maintaining the legacy code.
- Result
- The new service's account launched on Vue 2 and Vuex and became an argument for leaving the legacy code: the team decided to rewrite the project in Nuxt.
Metrics and results
- −37%regression bugs after the refactoring
- 5account sections in one app
- 16base UI components of the account
- ≤ 2.5 sLCP (Largest Contentful Paint)
- ≤ 200 msINP (Interaction to Next Paint)
- ≤ 0.1CLS (Cumulative Layout Shift)
CDEK.Shopping customers worked in the new account from day one, and the legacy refactoring cut regressions and sped up feature delivery.
Need a modern customer account in a legacy project?
I will embed an interactive account into your existing site without a separate frontend project, cover the legacy code with tests and split it into modules.