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.
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.
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.
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 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.
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
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).
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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:
Solid: the view library that provides rendering abstractions.
Vite (within Vinxi): the bundler to optimize code for execution in different runtimes.
Nitro (within Vinxi): the agnostic web server created by the Nuxt team and based on h3 with Rollup.
Vinxi: the orchestrator, what determines where the runtimes and the code each one has.
Seroval: the data serializer that will bridge data from server to browser and back.
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.
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.
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.
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.
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:
Differentiate by domains (i.e. hello.es, hello.jp)
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:
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.
exportdefault{
supportedLngs:["en","es"],
fallbackLng:"en",
defaultNS:"common",// Disabling suspense is recommended
react:{ useSuspense:false},};
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 apphydrateRoot(
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 createdimport i18n from"./i18n";// The configuration file we created...exportdefaultasyncfunctionhandleRequest(...){// We create a new instance of i18nextlet instance =createInstance();// We can detect the specific locale from each requestlet lng =await i18next.getLocale(request);// The namespaces the routes about to render wants to uselet 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"),},});returnnewPromise((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";...exportletloader=async({ request })=>{let locale =await i18next.getLocale(request);returnjson({ locale });};exportlet handle ={
i18n:"common",};exportdefaultfunctionApp(){// Get the locale from the loaderlet{ 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";exportdefaultfunctionMyPage(){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";...exportletloader=async({ request })=>{let t =await i18next.getFixedT(request);let title =t("intro");returnjson({ 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.
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.
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.
Using the splat route’s special param, let’s edit $.jsx file.
exportdefaultfunctionPage(){// useLoaderData returns JSON parsed data from loader funclet story =useLoaderData();
story =useStoryblokState(story,{
resolveRelations:["featured-posts.posts","selected-posts.posts"]});return<StoryblokComponent blok={story.content}/>};// loader is Backend API & Wired up through useLoaderDataexportconstloader=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 }=awaitgetStoryblokApi().get(`cdn/stories/${slug}`,
sbParams);returnjson(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.