Hey! I'm a frontend developer at ByteMinds. It's been six months since I joined the team and first encountered real production code.
Before this, my experience consisted of pet projects and courses where everything was usually "sterile." In this article, I want to share not just impressions, but real experience of "surviving" in a commercial project. This is an honest story of what to be prepared for if you're looking for your first job.
The Stack: Expectation vs Reality
When I was studying, I thought projects picked one modern framework (like React) and everything was strictly built with it. In the "ideal world" of tutorials, that's exactly how it works.
Reality: In commercial development, it's different. Right now I'm working on projects managed by CMS platforms (Umbraco and Optimizely). These systems are built on .NET, where most of the frontend is rendered server-side through Razor templates (CSHTML).
In this architecture, React isn't the "king" of the entire project. It's used selectively - as isolated components or blocks with complex logic that get embedded directly into Razor templates. The result is that on a single page, you might have several isolated React applications coexisting alongside markup that uses plain JavaScript scripts where needed. It turns out the ability to quickly switch between a framework's declarative approach and imperative vanilla JS is a crucial skill for "real combat."
Typical project situation: a page renders through Razor, you're tweaking card markup and adding simple logic with vanilla JS, and on the same screen there's an isolated React component handling something complex - filtering, state management, async requests. Within a single task, you constantly have to switch between these approaches and choose the right tool for the specific problem, rather than trying to force everything into React for the sake of "architectural purity."
The Codebase: Not "Bad Code" but a History of Decisions
A junior's first shock is legacy code. At first, it seems like the people who wrote this never heard of design patterns. But over time, understanding dawns: the codebase is a history. Some decisions were made in a rush before a release, others were driven by specific client requirements that changed three years ago.
Navigating these layers of different eras is an art in itself. That's where modern tools and colleagues come in handy.
AI as a Co-pilot: My Experience with Cursor
For me, Cursor has become an indispensable accelerator. I use it for pragmatic tasks where it's more efficient than manual searching:
- Context and navigation: It's great for understanding which files contain scattered logic and how components relate to each other in a massive project.
- Routine and boilerplate: Generating TypeScript types for an API or scaffolding a component structure - tasks that AI handles like a pro.
- Risk assessment: Before refactoring, you can ask: "Where is THIS used, and what will break if I delete or change it?"
Writing complex business logic? I wouldn't trust it with that yet, to be honest. AI is like an intern who works really fast but can confidently spout nonsense. So you still have to check every line of code.
Colleagues
Asking colleagues questions is one of the fastest ways to figure out a task. They often have context that isn't in the code: why a particular solution was chosen, what was tried before, and where the hidden pitfalls are.
These discussions not only save time but also help develop your "gut feeling." Gradually, you start to understand better where the real risks are and where you need to dig deeper, versus where you can accept the existing solution and move on.
In commercial development, this is critical: it's not just about writing code, but doing it safely for the project. Talking with colleagues accelerates your onboarding and helps you start thinking in the context of the product, not just an individual task.
Design and Communication
In pet projects, you're your own client. In commercial work, mockups are the foundation, but life throws curveballs. For example, a mockup shows 3 tags on a card, but the backend sends 7, and suddenly your layout starts to "dance."
It's important to understand that design isn't always the ultimate truth. Often, designers themselves see their work more as an aesthetic direction rather than a strict final result. They don't always know 100% what real content and in what quantities will come from the backend.
At that moment, responsibility falls on the developers. We're the ones who see the real data and have to decide how to display it as closely to the design as possible without breaking the UX. If in doubt, it's best to go to the designer and clarify their intent. But over time, you develop a feel for the boundaries of flexibility: where you can adapt the solution yourself, and where sign-off is crucial.
This approach teaches you to be more than just "hands" - it teaches you to be an engineer who thinks about the user and the product, and tries to solve a problem before it surfaces in production.
Sometimes You Have to Make Trade-offs
Things Don't Always Go to Plan: The Carousel Case
One of the most memorable moments was a task to implement a block that was a hybrid of a marquee and a regular slider. The designer kindly provided mockups with animation, approved by the client.
The requirements:
- Two rows of slides moving in the same direction, but at different speeds.
- Continuous movement (like a news ticker, linear flow), not slide-by-slide.
- Looped movement.
- Overall controls: autoplay on/off, next/previous slide.
- Slides are clickable cards.
The Technical Struggle
Initially, the project already used the Swiper library and had dozens of sliders implemented with it, so I decided not to add new dependencies. But, as it turned out, standard Swiper is designed for flipping through slides, not for linear flow (surprising, right?). To "squeeze" it to meet our needs, I had to search for hacks.
I found a configuration that turns the slider into a marquee:
typescript
const swiperDefaultOptions: SwiperOptions = {
modules: [Autoplay, FreeMode],
autoplay: {
delay: 0,
},
freeMode: {
enabled: true,
momentum: false,
momentumBounce: false,
},
loop: true,
slidesPerView: "auto",
spaceBetween: 24,
speed: 4000,
};
// Initialization
this.swiperTopCarousel = new Swiper(this.refs.topSwiper, {
...swiperDefaultOptions,
speed: Number(this.refs.topSwiper.dataset.speed) || swiperDefaultOptions.speed,
});
And of course, a bit of CSS magic was needed. For smooth scrolling and predictable behaviour:
css
.swiper-wrapper { transition-timing-function: linear; }
Seemed to work fine, the hack solved everything, but...
-
Janky loop when dragging. This became the main pain point. Because the slides had different widths, Swiper didn't calculate the "seam" point of cloned slides correctly. Visually, it looked like a jerk: the container would suddenly jump back to loop the animation. A possible fix would have been to calculate
slidesPerView precisely, but for the sake of responsiveness, we needed the auto value. The solution: we had to take away the user's ability to drag the slider. Harsh? Yes, but navigation buttons were still available. -
Stop on click. Clicking a slide would stop Swiper's autoplay. I never fully figured out why. People online who'd implemented this hack suggested
disableOnInteraction: falseandpointer-events: noneon the container. But that didn't work for us – the cards needed to be clickable.
The Solution: Compromise and a Second Library
I realised I was trying to force Swiper to do something it wasn't designed for. The ideal candidate seemed to be Splide. It has a built-in type: 'loop' mode with proper slide cloning, which solves the jerking issue. And the AutoScroll module smoothly moves the entire strip:
typescript
const baseOptions = {
type: 'loop',
autoWidth: true,
gap: gap,
arrows: false,
pagination: false,
drag: false, // disable drag, rely on autoscroll
clones: clones
};
this.topSlider = new Splide(this.refs.topSlider, {
...baseOptions,
autoScroll: {
speed: 0.4,
pauseOnHover: false,
pauseOnFocus: false
}
});
this.topSlider.mount({ AutoScroll });
Now came the dilemma: the project already had dozens of carousels using Swiper. Rewriting them all would be unjustifiably time-consuming and risky. Leaving the Swiper hack meant not delivering the task with the required quality.
In the end, I made the tough call: to add Splide as a second library specifically for this case. Yes, it increases the bundle size. But in this situation, it was the only way to achieve truly smooth animation without writing a custom solution from scratch.
When making such decisions, it's important to base them not just on code "beauty" and universality, but on the component's importance to the product. This carousel was the main visual feature of the case studies page and grabbed attention on first visit. Since the component directly impacted the first impression of the design, we decided not to compromise on the visuals.
Lesson learned: sometimes it's better to sacrifice bundle size for a solid UX and a stable solution, rather than maintaining fragile hacks in a critically important part of the interface.
On Estimates and Responsibility
Estimating tasks is pretty stressful at first. You allocate a certain amount of time, and you work towards it, but one unexpected carousel can eat up a good chunk of it due to unforeseen technical nuances.
This taught me that an estimate isn't just a number the project should be ready for. It's always about planning for risks, even when it seems like there couldn't possibly be any. Now I always try to build in a buffer for researching existing code and unexpected circumstances.
Conclusion
Over these six months, I've understood the main thing: commercial frontend isn't just about writing code in the latest React. It's about the ability to work with what's already there, to negotiate, and to find a balance between "beautiful code" and a working business. It's harder than it seems in courses, but also more interesting and varied. In the end, I'd highlight these key takeaways:
- Proactivity matters more than knowledge: If a task isn't clear - ask. It saves hours of wandering.
- Ideal code is sometimes the enemy of the product: In reality, you sometimes need to compromise to get a feature working stably and on time.
- Respect for Legacy: Instead of criticising "crappy" code, focus on improving it safely.
- Soft skills are key: Being able to explain your thoughts and accept feedback in code reviews makes you grow faster than just memorising syntax.
These were just the first six months. Real production is a noisy, non-linear thing, and school doesn't prepare you for it. But it's precisely in this chaos that you build the skills that make you a real developer.
Author: Yakov Shevcov, Frontend developer, ByteMinds


Top comments (0)