Payment of ₹ requested by HONEY VIG. Click the link to pay securely via Razorpay: Make 5000 payment Payment of ₹ requested by HONEY VIG. Click the link to pay securely via Razorpay: Make 5000 payment ⭐ If you would like to buy me a coffee, well thank you very much that is mega kind! : https://www.buymeacoffee.com/honeyvig
Hire a web Developer and Designer to upgrade and boost your online presence with cutting edge Technologies
Showing posts with label Frameworks. Show all posts
Showing posts with label Frameworks. Show all posts

Sunday, July 13, 2025

Building A Practical UX Strategy Framework

 

In my experience, most UX teams find themselves primarily implementing other people’s ideas rather than leading the conversation about user experience. This happens because stakeholders and decision-makers often lack a deep understanding of UX’s capabilities and potential. Without a clear UX strategy framework, professionals get relegated to a purely tactical role — wireframing and testing solutions conceived by others.

A well-crafted UX strategy framework changes this dynamic. It helps UX teams take control of their role and demonstrate real leadership in improving the user experience. Rather than just responding to requests, you can proactively identify opportunities that deliver genuine business value. A strategic approach also helps educate stakeholders about UX’s full potential while building credibility through measurable results.

Strategy And The Fat Smoker

When I guide teams on creating a UX strategy, I like to keep things simple. I borrow an approach from the book Strategy and the Fat Smoker and break strategy into three clear parts:

  1. First, we diagnose where we are today.
  2. Then, we set guiding policies to steer us.
  3. Finally, we outline actions to get us where we want to go.

Let me walk you through each part so you can shape a UX strategy that feels both practical and powerful.

Diagnosis: Know Your Starting Point

Before we outline any plan, we need to assess our current situation. A clear diagnosis shows where you can make the biggest impact. It also highlights the gaps you must fill.

Identify Status Quo Failures

Start by naming what isn’t working. You might find that your organization lacks a UX team. Or the team has a budget that is too small. Sometimes you uncover that user satisfaction scores are slipping. Frame these challenges in business terms. For example, a slow sign‑up flow may be costing you 20 percent of new registrations each month. That ties UX to revenue and grabs attention.

Once you have a list of failures, ask yourself:

What outcome does each failure hurt?

A slow checkout might reduce e‑commerce sales. Complicated navigation may dent customer retention. Linking UX issues to business metrics makes the case for change.

Map The Aspirational Experience #

Next, visualize what an improved journey would look like. A quick way is to create two simple journey maps. One shows the current experience. The other shows an ideal path. Highlight key steps like discovery, sign‑up, onboarding, and support. Then ask:

How will this new journey help meet our business goals?

Maybe faster onboarding can cut support costs. Or a streamlined checkout can boost average order value.

Let me share a real-world example. When working with the Samaritans, a UK mental health charity, we first mapped their current support process. While their telephone support was excellent, they struggled with email and text support, and had no presence on social media platforms. This was largely because volunteers found it difficult to manage multiple communication systems.

Mapping of the current experience of the Samaritan users
By mapping the current experience of the Samaritan users, we identified weaknesses that we could address in our UX strategy. (Large preview)

We then created an aspirational journey map showing a unified system where volunteers could manage all communication channels through a single interface. This clear vision gave the organization a concrete goal that would improve the experience for both users seeking help and the volunteers providing support.

Mapping of an aspirational experience
Mapping an aspirational experience provided a clear vision everybody could work towards. (Large preview)

This vision gives everyone something to rally around. It also guides your later actions by showing the target state.

Audit Resources And Influence

Next, turn your attention to what you have to work with. List your UX team members and their skills. Note any budget set aside for research tools or software licenses. Then identify where you have influence across the organization. Which teams already seek your advice? Who trusts your guidance? That might be the product group or marketing. You’ll lean on these allies to spread UX best practices.

Finally, consider who else matters. Are there policy owners, process leads, or executives you need on board? Jot down names and roles so you can loop them in later.

Spot Your Constraints

Every strategy must live within real‑world limits. Maybe there’s a headcount freeze. Or IT systems won’t support a major overhaul. List any technical, budget, or policy limits you face. Then accept them. You’ll design your strategy to deliver value without asking for impossible changes. Working within constraints boosts your credibility. It also forces creativity.

With the diagnosis complete, we know where we stand. Next, let’s look at how to steer our efforts.

Guiding Policies: Set the North Star

Guiding policies give you guardrails. They help you decide which opportunities to chase and which to skip. These policies reflect your priorities and the best path forward.

Choose A Tactical Or Strategic Approach

Early on, you must pick how your UX team will operate. You have two broad options:

  • Tactical
    You embed UX people on specific projects. They run tests and design interfaces hands‑on. This needs a bigger team. I like a ratio of one UX pro for every two developers.
  • Strategic
    You act as a center of excellence. You advise other teams. You build guidelines, run workshops, and offer tools. This needs fewer hands but a broader influence.

Weigh your resources against your goals. If you need to move fast on many projects, go tactical. If you want to shift mindsets, work strategically. Choose the approach with the best chance of success.

Define A Prioritization Method

You’ll face many requests for UX work. A clear way to sort them saves headaches. Over the years, I’ve used a simple digital triage. You score each request based on impact, effort, and risk. Then, you work on the highest‑scoring items first. You can adapt this model however you like. The point is to have a repeatable, fair way to say yes or no.

Create A Playbook Of Principles

A playbook holds your core design principles, standard operating procedures, and templates. It might include:

  • A design system for UI patterns;
  • Standards around accessibility or user research;
  • Guides for key tasks such as writing for the web;
  • Templates for common activities like user interviews.

This playbook becomes your team’s shared reference. It helps others repeat your process. It also captures the know‑how you need as your team grows.


A playbook helps you document your strategies, policies, principles, and standard operating procedures. (Large preview)

Plan Your Communication

Strategy fails when people don’t know about it. You need a plan to engage stakeholders. I find it helpful to use a RACI chart — who is Responsible, Accountable, Consulted, and Informed. Then decide:

  • How often will you send updates?
  • Which channels should you use (email, Slack, weekly demos)?
  • Who leads each conversation?

Clear, regular communication keeps everyone looped in. It also surfaces concerns early so you can address them.

With guiding policies in place, you have a clear way to decide what to work on. Now, let’s turn to making things happen.

Action Plan: Bring Strategy To Life

Actions are the concrete steps you take to deliver on your guiding policies. They cover the projects you run, the support you give, and the risks you manage.

Outline Key Projects And Services #

Start by listing the projects you’ll tackle. These might be:

  • Running a discovery phase for a new product.
  • Building a design system for your marketing team.
  • Conducting user tests on your main flow.

For each project, note what you will deliver and when. You can use your digital triage scores to pick the highest priorities. Keep each project scope small enough to finish in a few sprints. That way, you prove value quickly.

Offer Training And Tools

If you choose a strategic approach, you need to empower others. Plan workshops on core UX topics. Record short videos on testing best practices. Build quick reference guides. Curate a list of tools:

  • Prototyping apps,
  • Research platforms,
  • Analytics dashboards.

Make these resources easy to find in your playbook.

Assign Stakeholder Roles

Your strategy needs executive backing. Identify a senior sponsor who can break through roadblocks. Outline what you need them to do. Maybe it’s championing a new budget line or approving key hires. Also, pin down other collaborators. Who on the product side will help you scope new features? Who on the IT team will support user research tooling? Getting clear roles avoids confusion.

Manage Risks and Barriers

No plan goes off without a hitch. List your biggest risks, such as:

  • A hiring freeze delays tactical hires;
  • Key stakeholders lose interest;
  • Technical debt slows down new releases.

For each risk, jot down how you’ll handle it. Maybe you should shift to a fully strategic approach if hiring stalls. Or you can send a weekly one‑page update to reengage sponsors. Having a fallback keeps you calm when things go sideways.

Before we wrap up, let’s talk about making strategy stick.

Embedding UX Into The Culture

A strategy shines only if you deeply embed it into your organization’s culture. Here’s how to make that happen:

  • Build awareness and enthusiasm
    • Run regular “lunch and learn” sessions to showcase UX wins.
    • Host an annual UX day or mini-conference to boost visibility.
    • Create a monthly UX salon where teams share challenges and victories.
  • Make UX visible and tangible
    • Display personas and journey maps in office spaces.
    • Add design principles to everyday items like mousepads and mugs.
    • Share success metrics and improvements in company communications.
  • Embed UX into processes
    • Establish clear UX policies and best practices.
    • Review and update procedures that might hinder a good user experience.
    • Create a healthy competition between teams through UX metrics.

These tactics transform your strategy from a document into an organizational movement. They foster a culture where everyone thinks about user experience, not just the UX team. Remember, cultural change takes time — but consistent, visible efforts will gradually shift mindsets across the organization.

Implementing Your UX Strategy: From Plan To Practice

We started by diagnosing your current state. Then we set policies to guide your efforts. Finally, we laid out an action plan to deliver results. This three-part framework keeps your UX work tied to real business needs. It also gives you clarity, focus, and credibility.

However, creating a strategy is the easy part — implementing it is where the real challenge lies. This is precisely why the book Strategy and the Fat Smoker carries its distinctive title. Just as someone who is overweight or smokes knows exactly what they need to do, we often know what our UX strategy should be. The difficult part is following through and making it a reality.

Success requires consistent engagement and persistence in the face of setbacks. As Winston Churchill wisely noted,

“Success is going from failure to failure with no loss of enthusiasm.”

This perfectly captures the mindset needed to implement a successful UX strategy — staying committed to your vision even when faced with obstacles and setbacks

Monday, March 18, 2024

Web Development Is Getting Too Complex, And It May Be Our Fault

 An overwhelming number of frameworks and tooling available today gives the impression that web development has gotten perhaps too complex. As a newcomer, it can be frightening to have so many to consider, almost creating a fear of missing out that we see exploited to sell courses and tutorials on the new hot framework that you “cannot work without.” But perhaps that is just an exaggeration? Here it explores those claims and finds out if web development really is that complex and, most importantly, how we can prevent it from getting even more difficult than we already perceive it to be.

Front-end development seemed simpler in the early 2000s, didn’t it? The standard website consisted mostly of static pages made of HTML and CSS seasoned with a pinch of JavaScript and jQuery. I mean, who doesn’t miss the cross-browser compatibility days, right?

Fast forward to today, and it looks like a parallel universe is taking place with an overwhelming number of choices. Which framework should you use for a new project? Perhaps more established ones like React, Angular, Vue, Svelte, or maybe the hot new one that came out last month? Each framework comes with its unique ecosystem. You also need to decide whether to use TypeScript over vanilla JavaScript and choose how to approach server-side rendering (or static site generation) with meta-frameworks like Next, Nuxt, or Gatsby. And we can’t forget about unit and end-to-end testing if you want a bug-free web app. And we’ve barely scratched the surface of the front-end ecosystem!

But has it really gotten more complex to build websites? A lot of the frameworks and tooling we reach for today were originally crafted for massive projects. As a newcomer, it can be frightening to have so many to consider, almost creating a fear of missing out that we see exploited to sell courses and tutorials on the new hot framework that you “cannot work without.”

All this gives the impression that web development has gotten perhaps too complex. But maybe that is just an exaggeration? In this article, I want to explore those claims and find out if web development really is that complex and, most importantly, how we can prevent it from getting even more difficult than we already perceive it to be.

How It Was Before

As someone who got into web development after 2010, I can’t testify to my own experience about how web development was from the late 1990s through the 2000s. However, even fifteen years ago, learning front-end development was infinitely simpler, at least to me. You could get a website started with static HTML pages, minimal CSS for styling, and a sprinkle of JavaScript (and perhaps a touch of jQuery) to add interactive features, from toggled sidebars to image carousels and other patterns. Not much else was expected from your average developer beyond that — everything else was considered “going the extra mile.” Of course, the awesome native CSS and JavaScript features we have today weren’t around back then, but they were also unnecessary for what was considered best practice in past years.

Large and dynamic web apps certainly existed back then — YouTube and Facebook, to name a couple — but they were developed by massive companies. No one was expected to re-create that sort of project on their own or even a small team. That would’ve been the exception rather than the norm.

I remember back then, tend to worry more about things like SEO and page optimization than how my IDE was configured, but only to the point of adding meta tags and keywords because best practices didn’t include minifying all your assets, three shaking your code, caching your site on edge CDNs, or rendering your content on the server (a problem created by modern frameworks along hydration). Other factors like accessibility, user experience, and responsive layouts were also largely overlooked in comparison to today’s standards. Now, they are deeply analyzed and used to boost Lighthouse scores and impress search engine algorithms.

The web and everything around it changed as more capabilities were added and more and more people grew to depend on it. We have created new solutions, new tools, new workflows, new features, and whatever else new that is needed to cater to a bigger web with even bigger needs.

The web has always had its problems in the past that were worthy of fixing: I absolutely don’t miss tables and float layouts, along with messy DOM manipulation. This post isn’t meant to throw shade on new advances while waxing nostalgic about the good days of the “old wild web.” At the same time, though, yesterday’s problems seem infinitely simpler than those we face today.

JavaScript Frameworks

JavaScript frameworks, like Angular and React, were created by Google and Facebook, respectively, to be used in their own projects and satisfy the needs that only huge web-based companies like them have. Therein lies the main problem with web complexity: JavaScript frameworks were originally created to sustain giant projects rather than smaller ones. Many developers vastly underestimate the amount of time it takes to build a codebase that is reliable and maintainable with a JavaScript framework. However, the alternative of using vanilla JavaScript was worse, and jQuery was short for the task. Vanilla JavaScript was also unable to evolve quickly enough to match our development needs, which changed from simple informative websites to dynamic apps. So, many of us have quickly adopted frameworks to avoid directly mingling with JavaScript and its messy DOM manipulation.

Back-end development is a completely different topic, subject to its own complexities. I only want to focus on front-end development because that is the discipline that has perhaps overstepped its boundaries the most by bleeding into traditional back-end concerns.

Stacks Getting Bigger

It was only logical for JavaScript frameworks to grow in size over time. The web is a big place, and no one framework can cover everything. But they try, and the complexity, in turn, increases. A framework’s size seems to have a one-to-one correlation with its complexity.

But the core framework is just one piece of a web app. Several other technologies make up what’s known as a tech “stack,” and with the web gaining more users and frameworks catering to their needs, tech stacks are getting bigger and bigger. You may have seen popular stacks such as MEAN (MongoDB, Express, Angular, and Node) or its React (MERN) and Vue (MEVN) variants. These stacks are marketed as mature, test-proofed foundations suitable for any front-end project. That means the advertised size of a core framework is grossly underestimated because they rely on other micro-frameworks to ensure highly reliable architectures, as you can see in stackshare.io. Besides, there isn’t a one-size-fits-all stack; the best tool has always depended — and will continue to depend — on the needs and goals of your particular project.

This means that each new project likely requires a unique architecture to fulfill its requirements. Giant tech companies need colossal architectures across all their projects, and their stacks are highly engineered accordingly to secure scalability and maintenance. They also have massive customer bases, so maintaining a large codebase will be easier with more revenue, more engineers, and a clearer picture of the problem. To minimize waste, the tech stacks of smaller companies and projects can and should be minimized not only to match the scale of their needs but to the abilities of the developers on the team as well.

The idea that web development is getting too complex comes from buying into the belief that we all have the same needs and resources as giant enterprises.

“

Trying to imitate their mega stacks is pointless. Some might argue that it’s a sacrifice we have to make for future scalability and maintenance, but we should focus first on building great sites for the user without worrying about features users might need in the future. If what we are building is worth pursuing, it will reach the point where we need those giant architectures in good time. Cross that bridge when we get there. Otherwise, it’s not unlike wearing Shaquille O’Neal-sized sneakers in hopes of growing into them. They might not even last until then if it happens at all!

We must remember that the end-user experience is the focus at the end of the day, and users neither care about nor know what stack we use in our apps. What they care about is a good-looking, useful website where they can accomplish what they came for, not the technology we use to achieve it. This is how I’ve come to believe that web development is not getting more complex. It’s developers like us who are perpetuating it by buying into solutions for problems that do not need to be solved at a certain scale.

Let me be really clear: I am not saying that today’s web development is all bad. Indeed, we’ve realized a lot of great features, and many of them are thanks to JavaScript frameworks that have pushed for certain features. jQuery had that same influence on JavaScript for many, many years.

We can still create minimum viable products today with minimal resources. No, those might not make people smash the Like button on your social posts, but they meet the requirements, nothing more and nothing less. We want bigger! Faster! Cheaper! But we can’t have all three.

If anything, front-end development has gotten way easier thanks to modern features that solve age-old development issues, like the way CSS Flexbox and Grid have trivialized layouts that used to require complex hacks involving floats and tables. It’s the same deal with JavaScript gaining new ways to build interactions that used to take clever workarounds or obtuse code, such as having the Intersection Observer API to trivialize things like lazy loading (although HTML has gained its own features in that area, too).

We live in this tension between the ease of new platform features and the complexity of our stacks.

“

Do We Need A JavaScript Framework For Everything?

Each project, regardless of its simplicity, desperately needs a JavaScript framework. A project without a complex framework is like serving caviar on a paper plate.

At least, that’s what everyone seems to think. But is that actually true? I’d argue on the contrary. JavaScript frameworks are best used on bigger applications. If you’re working on a smaller project, a component-based framework will only complicate matters, making you split your website into a component hierarchy that amounts to overkill for small projects.

The idea of needing a framework for everything has been massively oversold. Maybe not directly, but you unconsciously get that feeling whenever a framework’s name pops in, as Edge engineer Alex Russell eloquently expresses in his article, “The Market For Lemons”:

“These technologies were initially pitched on the back of “better user experiences” but have utterly failed to deliver on that promise outside of the high-management-maturity organisations in which they were born. Transplanted into the wider web, these new stacks have proven to be expensive duds.”

— Alex Russell

Remember, the purpose of a framework is to simplify your life and save time. If the project you’re working on is smaller, the time you supposedly save is likely overshadowed by the time you spend either setting up the framework or making it work with the rest of the project. A framework can help make bigger web apps more interactive and dynamic, but there are times when a framework is a heavy-handed solution that actually breeds inefficient workflows and introduces technical debt.

Step back and think about this: Are HTML, CSS, and a touch of JavaScript enough to build your website or web application? If so, then stick with those. What I am afraid of is adding complexity for complexity’s sake and inadvertently raising the barrier to entry for those coming into web development. We can still accomplish so much with HTML and CSS alone, thanks again to many advances in the last decade. But we give the impression that they are unsuitable for today’s web consumption and need to be enhanced.

Knowing Everything And Nothing At The Same Time

The perceived standard that teams must adopt framework-centered architectures puts a burden not only on the project itself but on a developer’s well-being, too. As mentioned earlier, most teams are unable to afford those architectures and only have a few developers to maintain them. If we undermine what can be achieved with HTML and CSS alone and set the expectations that any project — regardless of size — needs to have a bleeding edge stack, then the weight to meet those expectations falls on the developer’s shoulders, with the great responsibility of being proficient in all areas, from the server and database to front end, to design, to accessibility, to performance, to testing, and it doesn’t stop. It’s what has been driving “The Great Divide” in front-end development, which Chris Coyier explains like this:

“The divide is between people who self-identify as a (or have the job title of) front-end developer yet have divergent skill sets. On one side, an army of developers whose interests, responsibilities, and skillsets are heavily revolved around JavaScript. On the other, an army of developers whose interests, responsibilities, and skillsets are focused on other areas of the front end, like HTML, CSS, design, interaction, patterns, accessibility, and so on.”

— Chris Coyier

Under these expectations, developers who focus more on HTML, CSS, design, and accessibility rather than the latest technology will feel less valued in an industry that appears to praise those who are concerned with the stack. What exactly are we saying when we start dividing responsibilities in terms of “full-stack development” or absurd terms like “10x development”? A while back, Brad Frost began distinguishing these divisions as “front-of-the-front-end” and “back-of-the-front-end”.

Mandy Michael explains what impact the chase for “full-stack” has had on developers trying to keep up:

“The worst part about pushing the “know everything” mentality is that we end up creating an industry full of professionals suffering from burnout and mental illness. We have people speaking at conferences about well-being, imposter syndrome, and full-stack anxiety, yet despite that, we perpetuate this idea that people have to know everything and be amazing at it.”

— Mandy Michael

This isn’t the only symptom of adopting heavy-handed solutions for what “vanilla” HTML, CSS, and JavaScript already handle nicely. As the expectations for what we can do as front-end developers grow, the learning curve of front-end development grows as well. Again, we can’t learn and know everything in this vast discipline. But we tell ourselves we have to, and thanks to this mentality, it’s unfortunately common to witness developers who may be extremely proficient with a particular framework but actually know and understand little of the web platform itself, like HTML semantics and structure.


The fact that many budding developers tend to jump straight into frameworks at the expense of understanding the basics of HTML and CSS isn’t a new worry, as Rachel Andrew discussed back in 2019:

“That’s the real entry point here, and yes, in 2019, they are going to have to move on quickly to the tools and techniques that will make them employable, if that is their aim. However, those tools output HTML and CSS in the end. It is the bedrock of everything that we do, which makes the devaluing of those with real deep skills in those areas so much more baffling.”

— Rachel Andrew

And I want to clarify yet again that modern Javascript frameworks and libraries aren’t inherently bad; they just aren’t designed to replace the web platform and its standards. But we keep pushing them like we want them to!

The Consequences Of Vendor Lock-In

“Vendor lock-in” happens when we depend too deeply on proprietary products and services to the extent that switching to other products and services becomes a nearly impossible task. This often occurs when cloud services from a particular company are deeply integrated into a project. It’s an issue, especially in cloud computing, since moving databases once they are set up is expensive and lengthy.

Vendor lock-in in web development has traditionally been restricted to the back end, like with cloud services such as AWS or Firebase; the front-end framework, meanwhile, was a completely separate concern. That said, I have noticed a recent trend where vendor lock-in is reaching into meta-frameworks, too. With the companies behind certain meta-frameworks offering hosting services for their own products, swapping hosts is increasingly harder to do (whether the lock-in is designed intentionally or not). Of course, companies and developers will be more likely to choose the hosting service of the company that made a particular framework used on their projects — they’re the experts! — but that only increases the project’s dependency on those vendors and their services.

A clear example is the relationship between Next and Vercel, the parent cloud service for Next. With the launch of Next 13, it has become increasingly harder to set up a Next project outside of Vercel, leading to projects like Open Next, which says right on its website that “[w]hile Vercel is great, it’s not a good option if all your infrastructure is on AWS. Hosting it in your AWS account makes it easy to integrate with your backend [sic]. And it’s a lot cheaper than Vercel.” Fortunately, the developers’ concerns have been heard, and Next 14 brings clarity on how to self-host Next on a Node server.

Another example is Gatsby and Gatsby Cloud. Gatsby has always offered helpful guides and alternative hosting recommendations, but since the launch of Gatsby Cloud in 2019, the main framework has been optimized so that using Gatsby and Gatsby Cloud together requires no additional hosting configurations. That’s fantastic if you adopt both, but it’s not so great if all you need is one or the other because integrating the framework with other hosts — and vice versa — is simply harder. It’s as if you are penalized for exercising choice.

And let’s not forget that no team expected Netlify to acquire Gatsby Cloud in February 2023. This is a prime case where the vendor lock-in problem hits everybody because converting from one site to another comes at a cost. Some teams were charged 120% more after converting from Gatsby Cloud to Netlify — even with the same plan they had with Gatsby Cloud!

What’s the solution? The common answer I hear is to stop using paid cloud services in favor of open-sourced alternatives. While that’s great and indeed a viable option for some projects, it fails to consider that an open-source project may not meet the requirements needed for a given app.

And even then, open-source software depends on the community of developers that maintain and update the codebase with little to no remuneration in exchange. Further, open source is equally prone to locking you into certain solutions that are designed to solve a deficiency with the software.

There are frameworks and libraries, of course, that are in no danger of being abandoned. React is a great example because it has an actively engaged community behind it. But you can’t have the same assurance with each new dependency you add to a project. We can’t simply keep installing more packages and components each time we spot a weak spot in the dependency chain, especially when a project is perfectly suited for a less complex architecture that properly leverages the web platform.

Choosing technology for your stack is an exercise of picking your own poison. Either choose a paid service and be subject to vendor lock-in in the future, or choose an open-source one and pray that the community continues to maintain it.

“

Those are virtually the only two choices. Many of the teams I know or have worked on depend on third-party services because they cannot afford to develop them on their own; that’s a luxury that only massive companies can afford. It’s a problem we have to undergo when starting a new project, but one we can minimize by reducing the number of dependencies and choosing wisely when we have to.

Each Solution Introduces A New Problem

Why exactly have modern development stacks gotten so large and complex? We can point a finger at the “Development Paradox.” With each new framework or library, a new problem crops up, and time-starved developers spend months developing a new tool to solve that problem. And when there isn’t a problem, don’t worry — we will create one eventually. This is a feedback loop that creates amazing solutions and technologies but can lead to over-engineered websites if we don’t reign it in.

This reminds me of the famous quote:

“The plain fact is that if you don’t have a problem, you create one. If you don’t have a problem, you don’t feel that you are living.”

— U.G. Krishnamurti

Let’s look specifically at React. It was originally created by Facebook for Facebook to develop more dynamic features for users while improving Facebook’s developer experience.

Since React was open-sourced in 2013 (and nearly re-licensed in 2017, if it weren’t for the WordPress community), hundreds of new utilities have been created to address various React-specific problems. How do you start a React project? There’s Create React App and Vite. Do you need to enhance your state management? There is Redux, among other options. Need help creating forms? There is a React Hook Form. And perhaps the most important question: Do you need server-side rendering? There’s Next, Remix, or Gatsby for that. Each solution comes with its own caveats, and developers will create their own solutions for them.

It may be unfair to pick on React since it considers itself a library, not a framework. It’s inevitably prone to be extended by the community. Meanwhile, Angular and Vue are frameworks with their own community ecosystems. And this is the tip of the iceberg since there are many JavaScript frameworks in the wild, each with its own distinct ideology and dependencies.

Again, I don’t want you to get the wrong idea. I love that new technologies emerge and find it liberating to have so many options. But when building something as straightforward as a webpage or small website — which some have started referring to as “multi-page applications” — we have to draw a line that defines how many new technologies we use and how reliable they are. We’re quite literally mashing together third-party code written by various third-party developers. What could go wrong? Please don’t answer that.

Remember that our users don’t care what’s in our stacks. They only see the final product, so we can save ourselves from working on unnecessary architectures that aren’t appreciated outside of development circles. It may seem counterintuitive in the face of advancing technology, but knowing that the user doesn’t care about what goes behind the scenes and only sees the final product will significantly enhance our developer experience and free you from locked dependencies. Why fix something that isn’t broken?

How Can We Simplify Our Codebases?

We’ve covered several reasons why web development appears to be more complex today than in years past, but blaming developers for releasing new utilities isn’t an accurate portrayal of the real problem. After all, when developing a site, it’s not like we are forced to use each new technology that enters the market. In fact, many of us are often unaware of a particular library and only learn about it when developing a new feature. For example, if we want to add toast notifications to our web app, we will look for a library like react-toastify rather than some other way of building them because it “goes with” that specific library. It’s worth asking whether the app needs toast notifications at all if they introduce new dependencies.

Imagine you are developing an app that allows users to discover, review, and rate restaurants in their area. The app needs, at a bare minimum, information about each restaurant, a search tool to query them, and an account registration flow with authentication to securely access the account. It’s easy to make assumptions about what a future user might need in addition to these critical features. In many cases, a project ends up delayed because we add unnecessary features like SSR, notifications, offline mode, and fancy animations — sometimes before the app has even converted its first registered user!

I believe we can boil down the complexity problem to personal wishes and perceived needs rather than properly scoping a project based on user needs and experiences.

That level of scope creep can easily turn into an over-engineered product that will likely never see the light of launching.


What can we do to simplify our own projects? The following advice is relevant when you have control over your project, either because it’s a personal one, it’s a smaller one for a smaller team, or you have control over the decisions in whatever size organization you happen to be in.

The hardest and most important step is having a sense of detection when your codebase is getting unnecessarily complicated. I deem it the hardest step because there is no certainty of what the requirements are or what the user needs; we can only make assumptions. Some are obvious, like assuming the user will need a way to log into the app. Others might be unclear, like whether the app should have private messaging between users. Others are still far-fetched, like believing users need extremely low latency in an e-commerce page. Other features are in the “nice to have” territory.

That is regarding the user experience, but the same questions emerge on the development side:

  • Should we be using a CSS preprocessor or a CSS framework, or can we achieve it using only CSS modules?
  • Is vanilla JavaScript enough, or are we going to add TypeScript?
  • Does the app need SSR, SSG, or a hybrid of the two?
  • Should we implement Redis on the back end for faster database queries, or is that too much scope for the work?
  • Should we be implementing end-to-end testing or unit tests?

These are valid questions that should be considered when developing a site, but they can distract us from our main focus: getting things done.

“Done is better than perfect.”

— Sheryl Sandberg

And, hey, even the largest and most sophisticated apps began as minimal offerings that iterated along the way.


We also ought to be asking ourselves what would happen if a particular feature or dependency isn’t added to the project. If the answer is “nothing,” then we should be shifting our attention to something else.

Another question worth asking: “Why are we choosing to add [X]?” Is it because that’s what is popular at the moment, or because it solves a problem affecting a core feature? Another aspect to take into consideration is how familiar we are with certain technologies and give preference to those we know and can start using them right away rather than having to stop and learn the ins and outs of a new framework.

Choose the right tool for the job, which is going to be the one that meets the requirements and fits your mental model. Focus less on a library’s popularity and scalability but rather on getting your app to the point where it needs to scale in the first place.

Conclusion

It’s incredibly difficult to not over-engineer web apps given current one-size-fits-all and fear-of-missing-out mentalities. But we can be more conscious of our project goals and exercise vigilance in guarding our work against scope creep. The same can be applied to the stack we use, making choices based on what is really needed rather than focusing purely on what everyone else is using for their particular work.

After reading the word “framework” exactly 48 times in this article, can we now say the web is getting too complex? It has been complex by nature since its origins, but complexity doesn’t translate to “over-engineered” web apps. The web isn’t intrinsically over-engineered, and we only have ourselves to blame for over-engineering our projects with overly-wrought solutions for perceived needs.

Wednesday, February 28, 2024

SolidStart: A Different Breed Of Meta-Framework

 Are you ready for a little exercise of pulling a framework apart and putting the pieces back together? In this article, Atila Fassina explains how meta-frameworks have evolved around core libraries in their own unique ways.

The current landscape of web tooling is increasingly more complex than ever before. We have libraries such as Solid, Vue, Svelte, Angular, React, and others that handle UI (User Interface) updates in an ergonomic fashion. The ever more important topic weighing on developers is the balance and trade-off of performance and usability best practices.

Developers are also blurring the lines between front-end and back-end code. The way we colocate logic and data is becoming more interesting as we integrate and mesh the way they work together to deliver a unified app experience.

With these shifts in ideology in mind, meta-frameworks have evolved around the core libraries in unique ways. To encapsulate the paradigms in which the UI is rendered and create seamless interoperability between our server code and our browser code, new practices are emerging.

While the initial idea of having a “meta” framework was to stitch together different sets of tools in order to build smooth experiences, it is tough to create integrations without making some level of opinionated decisions. So frameworks such as QwikCity, SvelteKit, Redwood, and Next.js went all the way into their own opinionated territory and provided a hard railway to ensure a defined set of conventions.

Meanwhile, others like Nuxt, Remix, and Analog stayed with a more shallow abstraction of their integrations, allowing a mix of their toolings and more easily using resources from the community (Vite is a good example of a tool that is shallowly used by all of them).

This not only produces a lower vendor lock-in to developers but also allows configuration to be re-used in some cases as such decisions are stripped out of opinions in favor of stronger abstractions. SolidStart takes a giant step beyond that into unbiased territory. Its own core is around 1500 lines of code, and the biggest pieces of functionality are provided with a meshing of well-integrated tools.

Modules Over Monoliths #

The impetus behind decoupling the architecture completely is to give power to the consuming developer to pick the framework and build it according to their desire. A developer may want to use Solid and SSR, but let’s imagine legacy code has a tight dependency on TanStack Router, while SolidStart and most Solid projects have Solid-Router instead. With a decoupled architecture, it becomes possible to either create an incremental adoption or integration layer so that everything will work tailored to the team’s best benefit.

The decoupled architecture sustaining newer frameworks also empowers the developer for a better debugging experience within and beyond its community. If an issue is encountered on the server, you’re not restricted to the knowledge of a specific framework.

For example, since both are based on Nitro, the Analog and SolidStart communities can now share knowledge with each other. Beyond that, because all of them are based in Nitro and Vite, Nuxt, Analog, and SolidStart can deploy to the same platforms and share implementation details to make each ecosystem grow together. The community wins with this approach, and the library/framework developers win as well. This is a radically new pattern and approach to jointly sharing the weight of meta-framework maintenance (one of the most feared responsibilities of maintainers).

What Is SolidStart Exactly? #

SolidStart is built from five fundamental pillars:

  1. Solid: the view library that provides rendering abstractions.
  2. Vite (within Vinxi): the bundler to optimize code for execution in different runtimes.
  3. Nitro (within Vinxi): the agnostic web server created by the Nuxt team and based on h3 with Rollup.
  4. Vinxi: the orchestrator, what determines where the runtimes and the code each one has.
  5. Seroval: the data serializer that will bridge data from server to browser and back.
the anatomy of SolidStart
The anatomy of SolidStart.

1. Solid #

Solid as a rendering library has become increasingly popular because of its incredible rendering performance and thin abstraction layer. It’s built on top of Signals, a renewing and modern implementation of the classical Observer Pattern, and provides a series of helpers to empower the developer to create extremely high-performance and easy-to-read code.

It uses JSX and has syntax that is very similar to React, but under the hood, it operates in a completely different manner. Bringing the developer closer to the DOM while still providing the needed ergonomics to be productive in the developer environment. At only 3Kb of bundle size, it’s often a choice even for mostly static sites. e.g., many people use Solid to bring interactivity to their content-based Astro websites when needed.

Solid also brings first-class primitives, built-in Control Flow components, high-quality state management, and full TypeScript support. Solid packs a punch in a small efficient package.

2. Vite #

Arguably the best bundler in the JavaScript ecosystem, Vite has the right balance between declarative and customizable configuration. It’s fully based on TypeScript, which makes it easy to extend by the consuming library or framework, and has a large enough user base that guarantees its versatility. Vite works with and has become the de-facto tool for many frameworks, such as Astro, Vue, Preact, Elm, Lit, Svelte, Nuxt, Analog, Remix, and many others.

Aside from its popularity, it is particularly loved for its fast server start time, HMR support, optimized builds, ease of configuration, rich plug-in ecosystem, modern tooling, and high-quality overall developer experience.

3. Nitro #

A framework in itself, Nitro is written in TypeScript and is completely agnostic and open for every meta-framework to use as a foundation. It provides a powerful set of tools and APIs to manage caching, routes, and tree-shaking. It is a fast base for any JavaScript-based project to build their server on. Nitro is highly scalable, integrating easily into DevOps and CI/CD pipelines, security-focused, robust, and boasts a rich set of adapters, making it deployable on most, if not all, major vendor platforms.

Think of Nitro as a bolt-on extension that makes Vite easier to build on and more pliable. It solves a majority of run-time level concerns that would need to be solved in Vite.

4. Vinxi #

Vinxi is an SDK (Software Development Kit) that brings a powerful set of configuration-based tools to create full-stack applications. It composes Nitro under the hood to establish a web server and leverages Vite for the bundling components. It is inspired by the Bun App API and works via a very declarative interface to instantiate an app by setting routers for each runtime.

For example:

import { createApp } from "vinxi";
import solid from "vite-plugin-solid";

const resources = {
    name: "public",
    mode: "static",
    dir: "./public",
};

const spa = {
    name: "client",
    mode: "build",
    handler: "./app/client.tsx",
    target: "browser",
    plugins: () => [solid({ ssr: true })],
    base: "/_build"
}

const server = {
    name: "ssr",
    mode: "handler",
    handler: "./app/server.tsx",
    target: "server",
    plugins: () => [solid({ ssr: true })],
}

export default createApp({
    routers: [resources, spa, server],
});

As resource routes work as a bucket, by defining mode: "static" there’s no need to define a handler. Your router can also be statically built (mode: “build”) and targeted towards the browser runtime, or it can be on the server and handle each request via its entry-point handler: "./app/server.tsx".

Vinxi will leverage the right set of APIs from Nitro and Vite so your resources aren’t exposed to the wrong runtimes and so that deployment works smoothly for defined platform providers.

5. Seroval #

Once routers are set, and the app can handle navigation (hard navigation via the “ssr” handler and soft navigation via the “client” handler), it’s time to stitch them together. This is the part where SolidStart’s core comes into place. It supplies APIs that deliver the ergonomics to fetch and mutate requests.

All these APIs are powered by yet another agnostic library called Seroval. In order to send data between server and client in a secure manner, it all needs to be serialized. Seroval defines itself in an over-simplistic way: “Stringify JS Values.” However, this definition doesn’t address the fact it does so in an extremely powerful and fast fashion.

Thanks to Seroval, SolidStart is able to safely and efficiently cross the serialization boundary. Resource serialization is arguably the most important feature of a full-stack framework — without it, the back-end and front-end bridge simply won’t work in a smooth way.

Besides resource serialization, SolidStart can also use server actions. Straight from the documentation, this is how server actions look for us (note the "use server" directive that allows Vinxi to put the code in the correct place.

import { action, redirect } from "@solidjs/router";
 
const isAdmin = action(async (formData: FormData) => {
  "use server";
  await new Promise((resolve, reject) => setTimeout(resolve, 1000));
  const username = formData.get("username");
  if (username === "admin") throw redirect("/admin");
  return new Error("Invalid username");
});
 
export function MyComponent() {
  return (
    <form action={isAdmin} method="post">
      <label for="username">Username:</label>
      <input type="text" name="username" />
      <input type="submit" value="submit" />
    </form>
  );
}

Everything Comes Together #

After this breakdown, things may still be a bit up in the air. So, let’s close the loop by assembling the parts:

SolidStart explained

Hopefully, this little exercise of pulling the framework apart and putting the pieces back together was interesting and insightful. Let me know in the comments below or on X if this has helped you better understand or even choose the tools for your next project.

Friday, January 19, 2024

SolidStart: A Different Breed Of Meta-Framework

 Are you ready for a little exercise of pulling a framework apart and putting the pieces back together? In this article, Atila Fassina explains how meta-frameworks have evolved around core libraries in their own unique ways.

The current landscape of web tooling is increasingly more complex than ever before. We have libraries such as Solid, Vue, Svelte, Angular, React, and others that handle UI (User Interface) updates in an ergonomic fashion. The ever more important topic weighing on developers is the balance and trade-off of performance and usability best practices.

Developers are also blurring the lines between front-end and back-end code. The way we colocate logic and data is becoming more interesting as we integrate and mesh the way they work together to deliver a unified app experience.

With these shifts in ideology in mind, meta-frameworks have evolved around the core libraries in unique ways. To encapsulate the paradigms in which the UI is rendered and create seamless interoperability between our server code and our browser code, new practices are emerging.

While the initial idea of having a “meta” framework was to stitch together different sets of tools in order to build smooth experiences, it is tough to create integrations without making some level of opinionated decisions. So frameworks such as QwikCity, SvelteKit, Redwood, and Next.js went all the way into their own opinionated territory and provided a hard railway to ensure a defined set of conventions.

Meanwhile, others like Nuxt, Remix, and Analog stayed with a more shallow abstraction of their integrations, allowing a mix of their toolings and more easily using resources from the community (Vite is a good example of a tool that is shallowly used by all of them).

This not only produces a lower vendor lock-in to developers but also allows configuration to be re-used in some cases as such decisions are stripped out of opinions in favor of stronger abstractions. SolidStart takes a giant step beyond that into unbiased territory. Its own core is around 1500 lines of code, and the biggest pieces of functionality are provided with a meshing of well-integrated tools.

Modules Over Monoliths

The impetus behind decoupling the architecture completely is to give power to the consuming developer to pick the framework and build it according to their desire. A developer may want to use Solid and SSR, but let’s imagine legacy code has a tight dependency on TanStack Router, while SolidStart and most Solid projects have Solid-Router instead. With a decoupled architecture, it becomes possible to either create an incremental adoption or integration layer so that everything will work tailored to the team’s best benefit.

The decoupled architecture sustaining newer frameworks also empowers the developer for a better debugging experience within and beyond its community. If an issue is encountered on the server, you’re not restricted to the knowledge of a specific framework.

For example, since both are based on Nitro, the Analog and SolidStart communities can now share knowledge with each other. Beyond that, because all of them are based in Nitro and Vite, Nuxt, Analog, and SolidStart can deploy to the same platforms and share implementation details to make each ecosystem grow together. The community wins with this approach, and the library/framework developers win as well. This is a radically new pattern and approach to jointly sharing the weight of meta-framework maintenance (one of the most feared responsibilities of maintainers).

What Is SolidStart Exactly?

SolidStart is built from five fundamental pillars:

  1. Solid: the view library that provides rendering abstractions.
  2. Vite (within Vinxi): the bundler to optimize code for execution in different runtimes.
  3. Nitro (within Vinxi): the agnostic web server created by the Nuxt team and based on h3 with Rollup.
  4. Vinxi: the orchestrator, what determines where the runtimes and the code each one has.
  5. Seroval: the data serializer that will bridge data from server to browser and back.
the anatomy of SolidStart
The anatomy of SolidStart.

1. Solid #

Solid as a rendering library has become increasingly popular because of its incredible rendering performance and thin abstraction layer. It’s built on top of Signals, a renewing and modern implementation of the classical Observer Pattern, and provides a series of helpers to empower the developer to create extremely high-performance and easy-to-read code.

It uses JSX and has syntax that is very similar to React, but under the hood, it operates in a completely different manner. Bringing the developer closer to the DOM while still providing the needed ergonomics to be productive in the developer environment. At only 3Kb of bundle size, it’s often a choice even for mostly static sites. e.g., many people use Solid to bring interactivity to their content-based Astro websites when needed.

Solid also brings first-class primitives, built-in Control Flow components, high-quality state management, and full TypeScript support. Solid packs a punch in a small efficient package.

2. Vite

Arguably the best bundler in the JavaScript ecosystem, Vite has the right balance between declarative and customizable configuration. It’s fully based on TypeScript, which makes it easy to extend by the consuming library or framework, and has a large enough user base that guarantees its versatility. Vite works with and has become the de-facto tool for many frameworks, such as Astro, Vue, Preact, Elm, Lit, Svelte, Nuxt, Analog, Remix, and many others.

Aside from its popularity, it is particularly loved for its fast server start time, HMR support, optimized builds, ease of configuration, rich plug-in ecosystem, modern tooling, and high-quality overall developer experience.

3. Nitro

A framework in itself, Nitro is written in TypeScript and is completely agnostic and open for every meta-framework to use as a foundation. It provides a powerful set of tools and APIs to manage caching, routes, and tree-shaking. It is a fast base for any JavaScript-based project to build their server on. Nitro is highly scalable, integrating easily into DevOps and CI/CD pipelines, security-focused, robust, and boasts a rich set of adapters, making it deployable on most, if not all, major vendor platforms.

Think of Nitro as a bolt-on extension that makes Vite easier to build on and more pliable. It solves a majority of run-time level concerns that would need to be solved in Vite.

4. Vinxi

Vinxi is an SDK (Software Development Kit) that brings a powerful set of configuration-based tools to create full-stack applications. It composes Nitro under the hood to establish a web server and leverages Vite for the bundling components. It is inspired by the Bun App API and works via a very declarative interface to instantiate an app by setting routers for each runtime.

For example:

import { createApp } from "vinxi";
import solid from "vite-plugin-solid";

const resources = {
    name: "public",
    mode: "static",
    dir: "./public",
};

const spa = {
    name: "client",
    mode: "build",
    handler: "./app/client.tsx",
    target: "browser",
    plugins: () => [solid({ ssr: true })],
    base: "/_build"
}

const server = {
    name: "ssr",
    mode: "handler",
    handler: "./app/server.tsx",
    target: "server",
    plugins: () => [solid({ ssr: true })],
}

export default createApp({
    routers: [resources, spa, server],
});

As resource routes work as a bucket, by defining mode: "static" there’s no need to define a handler. Your router can also be statically built (mode: “build”) and targeted towards the browser runtime, or it can be on the server and handle each request via its entry-point handler: "./app/server.tsx".

Vinxi will leverage the right set of APIs from Nitro and Vite so your resources aren’t exposed to the wrong runtimes and so that deployment works smoothly for defined platform providers.

5. Seroval

Once routers are set, and the app can handle navigation (hard navigation via the “ssr” handler and soft navigation via the “client” handler), it’s time to stitch them together. This is the part where SolidStart’s core comes into place. It supplies APIs that deliver the ergonomics to fetch and mutate requests.

All these APIs are powered by yet another agnostic library called Seroval. In order to send data between server and client in a secure manner, it all needs to be serialized. Seroval defines itself in an over-simplistic way: “Stringify JS Values.” However, this definition doesn’t address the fact it does so in an extremely powerful and fast fashion.

Thanks to Seroval, SolidStart is able to safely and efficiently cross the serialization boundary. Resource serialization is arguably the most important feature of a full-stack framework — without it, the back-end and front-end bridge simply won’t work in a smooth way.

Besides resource serialization, SolidStart can also use server actions. Straight from the documentation, this is how server actions look for us (note the "use server" directive that allows Vinxi to put the code in the correct place.

import { action, redirect } from "@solidjs/router";
 
const isAdmin = action(async (formData: FormData) => {
  "use server";
  await new Promise((resolve, reject) => setTimeout(resolve, 1000));
  const username = formData.get("username");
  if (username === "admin") throw redirect("/admin");
  return new Error("Invalid username");
});
 
export function MyComponent() {
  return (
    <form action={isAdmin} method="post">
      <label for="username">Username:</label>
      <input type="text" name="username" />
      <input type="submit" value="submit" />
    </form>
  );
}

Everything Comes Together

After this breakdown, things may still be a bit up in the air. So, let’s close the loop by assembling the parts:


Hopefully, this little exercise of pulling the framework apart and putting the pieces back together was interesting and insightful. Let me know in the comments below or on X if this has helped you better understand or even choose the tools for your next project.

Monday, April 3, 2023

Getting Internationalization (i18n) Right With Remix And Headless CMS

 This article will show you the impact of internationalization, its fundamental logic, how to approach it with Remix, and optionally, how to manage it more conveniently using a headless CMS.

How much of a language barrier is there still in the 21st century? You, as the reader, are probably very familiar with English, but what about others?

Nowadays, most of us have often heard the importance of accessibility, better performance, and better UX or DX. You might not hear or often see about i18n compared with these topics. But if you see facts and numbers from the statistics, you might find some surprising results about i18n and its impact. Let’s find out about that together.

i18n And l10n

Before we go through the impact of i18n, let’s learn the difference between the two terminologies.

  • i18n
    i18n stands for internationalization. Between the first character, “i,” and the last character, “n,” from this word, there are 18 characters. i18n describes implementing the structures and features for your applications to be ready to localize content.
  • l10n
    l10n stands for localization. Between the first character, “l,” and the last character, “n,” from this word, there are ten characters. l10n means translating content into the languages for users who are accessing from specific regions.

As a follow-up, i18n contains a programmatic process to implement features for content editors and translators to be able to start the l10n process from the UI.

Why Does i18n Matters That Much?

To see the importance of i18n, let’s look at the numbers and statistics for objective information. You will see the numbers of some facts below, and before reading further, let’s guess what those numbers stand for.

  • 5.07 billion
  • 25.9%
  • 74.1%
  • China
  • Asia

The first fact shows a tremendous amount of the numbers. 5.07 billion is the number of users in this world in 2020. The world population in 2021 was 7.837 billion, nearly 8 billion. More than half of the world’s population has access to content on the internet and apps.

Based on the number of users in this world, there’s another research about the most common languages used on the internet. Looking at the chart, most of us pay attention to the highest number on this diagram: 25.9%, English.

The second highest is the rest of the languages, 23.1%. Also, suppose you gather the rest of the percentage except English. In that case, you may realize out of the over 5 billion users, 74.1% of the users are accessing the content in any other language.

After going through these facts, we can now talk about why internationalizing and localizing your content for Asia and China, in particular, is crucial. China has the most internet users worldwide. As a result, more than half of all internet users globally are from Asia.

Based on what we saw, probably, we can not ignore localizing content. It will improve UX if these huge amounts of users worldwide could have localized content. After knowing the potential impact of proper i18n, let’s look at the fundamental logic.

How i18n Works At A Basic Level

Regardless of the technology to implement i18n, there are three ways to determine languages and regions.

  1. The location of the IP address
  2. Accept-Language header/Navigator.languages
  3. Identifiers in URLs

Using the IP address detects the region of the users and allows them to access content in their regional languages. However, the user’s IP address does not necessarily match their language preference. Moreover, location analysis prevents the sites from being crawled by search engines.

Using Accept-Language header or Navigator.languages is another possible approach to implement i18n. However, this approach provides language information but not regional information.

i18n is not just about localizing content. It includes improving UX as well. For example, creating identifiers in URLs enhances UX. It also helps to divide localized content into the dedicated system. We will cover how it’s possible to implement such a system in the “A Combination Of Remix And CMS” section.

Typically, identifiers in URLs exist in three different patterns:

  1. Differentiate by domains (i.e. hello.es, hello.jp)
  2. URL parameters (i.e. hello.com?loc=de)
  3. Localized sub-directories (i.e. hello.com/es, hello.com/ja)

To follow the same-origin policy for better SEO, localized sub-directories can be used.

Based on the interesting facts and the fundamental logic for implementing i18n, we’ll talk about frameworks and libraries, as some of them use i18n libraries.

Libraries

In order to not reinvent the wheel whenever we want to implement i18n into our projects, developers have different libraries, tools, and services that can be used to facilitate the work. If we are working with React or React-based frameworks, we have different options available. Let’s talk about some of them.

Format.js 

Format.js is a modular collection of JavaScript libraries that we can use to implement i18n logic in both the client and the server. This group of libraries is focused on formatting numbers, dates, and strings. It offers different functionalities and tooling and runs in the browser as well as in the Node.js runtime. It integrates with various frameworks, like Vue and React, so that we can use its functionalities on our Remix projects. You can read more about it on the official React Intl’s docs.

i18next

Another alternative that we can evaluate for our project is i18next. This JavaScript library goes beyond the standard i18n features, providing a whole suite to manage i18n in our projects. We can detect users’ language, cache translations, and even install plugins and extensions. As it was built in JavaScript, we can use this tool for websites as well as for mobile and desktop applications.

What About Remix?

When creating a website using Remix, we have different options to consider. As it is a React-based framework, we can use any of the previously mentioned libraries. However, we will go through two approaches that can fit better in your Remix projects. First, we will see how to localize content using remix-i18next, a Remix specific library for i18n. Second, we will use a headless content management system as the source of our content’s different languages/locales.

remix-i18next

Based on i18next, Sergio Xalambrí, one of the main Remix contributors, created remix-i18next. This library offers similar features and modules as the JavaScript library but focuses on Remix concepts and approaches. Easy to set up and use, production-ready, and without any requirement or dependency. Let’s have a closer look at how to implement i18n into our Remix projects using remix-i18next.

First of all, we need to install some npm packages:

npm install remix-i18next i18next react-i18next i18next-browser-languagedetector i18next-http-backend i18next-fs-backend

All of them will help us to manage i18n on both the server and the client side of our website. We will also use them to set up our backend and define the logic that will detect the language from the user.

Now, we should add some configuration that will be used across the website from both the client and the server side. Let’s create a couple of JSON files with the translations of the different character strings that we’ll use on our website:

{
  "intro": "Hello everyone!"
}
{
  "intro": "Hola a todos!"
}

By naming the files “common.json”, we’re defining the namespace for the strings that we’ll list in them.

Now, let’s create a file called i18n.js. This file contains different configuration settings that we’ll use at the moment of initializing our i18n server.

export default {
  supportedLngs: ["en", "es"],
  fallbackLng: "en",
  defaultNS: "common",
  // Disabling suspense is recommended
  react: { useSuspense: false },
};

You can see more configuration options in the official i18next docs.

Now, create the file i18next.server.js, which contains logic that will be used in the entry.server.jsx file of our Remix project.

import Backend from "i18next-fs-backend";
    import { resolve } from "node:path";
    import { RemixI18Next } from "remix-i18next";
    import i18n from "~/i18n"; // The configuration file we created
    
    let i18next = new RemixI18Next({
      detection: {
        supportedLanguages: i18n.supportedLngs,
        fallbackLanguage: i18n.fallbackLng,
      },
      i18next: {
        ...i18n,
        backend: {
          loadPath: resolve('./public/locales/{{lng}}/{{ns}}.json'),
        },
      },
      backend: Backend,
    });
    
    export default i18next;
We’re basically initializing a new i18n server that will run with our Remix backend. We’re specifying the location of the JSON files containing the translations to be used. Let’s add these features to our main Remix config files. First, we add some logic to be able to translate content client side. To do that, let’s edit our `entry.client.jsx` file:
import i18next from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import Backend from "i18next-http-backend";
import { I18nextProvider, initReactI18next } from "react-i18next";
import { getInitialNamespaces } from "remix-i18next";
import i18n from "./i18n"; // The configuration file we created

i18next
  .use(initReactI18next)
  .use(LanguageDetector)
  .use(Backend)
  .init({
    ...i18n, // The same config we created for the server
    ns: getInitialNamespaces(),
    backend: {
      loadPath: "/locales/{{lng}}/{{ns}}.json",
    },
    detection: {
      order: ["htmlTag"],
      caches: [],
    },
  })
  .then(() => {
    // After i18next init, hydrate the app
    hydrateRoot(
      document,
      // Wrap RemixBrowser in I18nextProvider
      <I18nextProvider i18n={i18next}>
        <RemixBrowser />
      </I18nextProvider>
    );
  });

We need to wait to ensure translations are loaded before the hydration in order to keep our web app interactive.

Let’s add the logic to the entry.server.jsx file now:

import { createInstance } from "i18next";
import Backend from "i18next-fs-backend";
import { resolve } from "node:path";
import { I18nextProvider, initReactI18next } from "react-i18next";
import i18next from "./i18next.server"; // The backend file we created
import i18n from "./i18n"; // The configuration file we created

...

export default async function handleRequest(
...
) {
  // We create a new instance of i18next
  let instance = createInstance();

  // We can detect the specific locale from each request
  let lng = await i18next.getLocale(request);
  // The namespaces the routes about to render wants to use
  let ns = i18next.getRouteNamespaces(remixContext);

  await instance
    .use(initReactI18next)
    .use(Backend)
    .init({
      ...i18n,// The config we created
      lng, // The locale we detected from the request
      ns,
      backend: {
        loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"),
      },
    });

  return new Promise((resolve, reject) => {
    ...

    let { pipe, abort } = renderToPipeableStream(
      
        {" "}
      ,
      ...
    );
    ...
  });
}

Identifying users’ preferred language will allow us, among other things, to redirect them to certain routes.

Now we can start using the functionalities provided by remix-i18next to detect the user’s locale and deliver translated content based on that. Let’s edit the root.jsx file:

...

import { json } from "@remix-run/node";
import { useChangeLanguage } from "remix-i18next";
import { useTranslation } from "react-i18next";
import i18next from "~/i18next.server";

...

export let loader = async ({ request }) => {
  let locale = await i18next.getLocale(request);
  return json({ locale });
};

export let handle = {
  i18n: "common",
};

export default function App() {
  // Get the locale from the loader
  let { locale } = useLoaderData();
  let { i18n } = useTranslation();

  useChangeLanguage(locale);

  return (
    <html lang={locale} dir={i18n.dir()}>
      ...
    </html>
  );
}

The useChangeLanguage hook will change the language of the instance to the locale detected by the loader. Whenever we do something to change the language, this locale will change, and i18next will load the correct translations.

Now, we are able to translate content in any route:

import { useTranslation } from "react-i18next";

export default function MyPage() {
  let { t } = useTranslation();
  return <h1>{t("intro")}</h1>;
}

We use the t() function to show translated strings based on the list of messages that we defined in our JSON files.

In this example, we use one default namespace, but we can set up multiple namespaces if we want. You can read more about the t() function in the official i18next docs.

In case we want to translate content server side, we can use the getFixedT method inside our loaders and actions:

import i18next from "~/i18next.server";

...

export let loader = async ({ request }) => {
  let t = await i18next.getFixedT(request);
  let title = t("intro");
  return json({ title });
};

A Combination Of Remix And CMS

Together, we explored the available options to implement i18n with Remix. At the beginning of this article, we learned that i18n could result in hugely improved UX and SEOUX, and SEO. As part of UX, it’s also important to include better DX.

The approach above creates translation files at the source code level. Also, we don’t have the logic to implement identifiers in URLs. To achieve this, let’s look at the approach of integrating a CMS. In this article, we’ll use Storyblok, which offers three different approaches to localizing content and handles to determine the languages and regions.

Note: If you want to create the connection between your Remix app and Storyblok, there’s a 5-minute tutorial that explains just how to do that.

After that, you can quickly clone a starter space by using this magic link to have all the necessary components and field types. This example space covers an approach called folder-level translation. We’ll cover what it is about in the next section.

Choose Between Three Approaches #

Storyblok has three ways to create the layout to store localized content and determine languages and regions.

  1. Folder-level translation: Divide localized content in folder-level.
  2. Field-level translation: Translate in field-type level.
  3. Space-level translation: Dedicate spaces (environments or repositories) into certain localized content.

To cover the identifiers in URLs, folder-level translation works perfectly, as each folder will only contain relevant localized content.

Also, identifiers can be modified from the folder settings via the slug.

By modifying the slug from the folder settings screen, this localized identifier in the URL appears in all stories inside this Japanese folder. For example, the about page inside of the Japanese folder already has a localized identifier in the URL.

To programmatically generate content pages, Remix has a feature called Splats, catching all slugs regardless of the nested levels. Naming a file $.jsx will enable the catch-all slug fundamental function.

app
├── root.jsx
└── routes
    ├── files
    │   └── $.jsx
    └── files.jsx

The difference between dynamic segments from Remix is that splats still match at the next /. Therefore, splats will capture everything in the path. If the URL path is hello.com/ja/about/something, the splat route has a special param to capture the trailing segments of the URL.

export async function loader({ params }) {
  params["*"]; // "ja/about/something"
}

Using the splat route’s special param, let’s edit $.jsx file.

export default function Page() {
 // useLoaderData returns JSON parsed data from loader func
 let story = useLoaderData();
 story = useStoryblokState(story, {
   resolveRelations: ["featured-posts.posts", "selected-posts.posts"]
 });
 return <StoryblokComponent blok={story.content} />
};
// loader is Backend API & Wired up through useLoaderData
export const loader = async ({ params, preview = false }) => {
 let slug = params["*"] ?? "home";
 slug = slug.endsWith("/") ? slug.slice(0, -1) : slug;
 let sbParams = {
   version: "draft",
   resolve_relations: ["featured-posts.posts", "selected-posts.posts"],
 };
 // …
 let { data } = await getStoryblokApi().get(`cdn/stories/${slug}`,
 sbParams);
 return json(data?.story, preview);
};

HINT: In the section “Choose between 3 approaches”, we didn’t cover all three approaches, but if you’d like to know more, all approaches are documented below.

Summary

We learned together the facts and statistics to know the impacts and the importance of i18n and saw how Remix handles several options to implement advanced i18n. Interestingly, a better i18n experience provides better SEO and UX. Hopefully, this article provided you with new knowledge and insightful learnings.