Skip to content

Internationalization (I18N) in Angular with Transloco

Internationalization (I18N) in Angular with Transloco

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

In this post, I'm going to talk about internationalization and how to implement it in an Angular application with the help of a neat library called Transloco.

What is Internationalization (I18N)?

I think it's best to start by explaining what internationalization (i18n for short - first letter of the word followed by the number of letters until the last letter of the word) is and why it is so important.

Internationalization is the process of adapting a software application to allow it to be easily used in multiple countries. There is a common misconception that this means that all text within an application must have translations for multiple languages; however, full i18n goes beyond just plain text in your application.

In many western countries, red is commonly used to signal danger. However, in some regions throughout the world, red is seen as a positive color.
Likewise, in many western countries, text is read from left to right; however, in some languages text is read from right to left.

Another example of where i18n comes into play is in images. If an image in your application contains text, to fully support i18n, it may be appropriate to generate multiple images each with the correct translations for the languages you support in your application. You would then need to serve the correct image depending on what country the user using your application is located in.

Naomi Meyer gave a brilliant talk on this subject at AngularConnect. You can watch that talk here if you are interested in learning more about. It's worth watching!

What is Transloco?

Transloco is a library developed and maintained by the NgNeat team. It contains a multitude of features and officially supported plugins to help make translating your Angular applications easy, maintainable, scalable and performant. It is actively maintained and it supports:

  • Lazy loading
  • Multiple fallbacks
  • Server-side rendering
  • Localization (l10n)
  • Runtime language changing
  • Multiple languages simultaneously
  • Pluralization (through an official plugin)

It's very easy to set up and use as we'll see in the next part of this post!

Integrating Transloco into an Angular Application

Now it's time to get our hands into some code! Let's start with a clean slate. I'll assume you have nodejs and npm installed already. If not, I recommend using the LTS version!

Installation

If you don't already have the Angular CLI installed globally, run the following command in your favourite shell:

npm install -g @angular/cli

This will install the Angular CLI globally. Next, we'll create a new app using the Angular CLI:

ng new transloco-test

You'll be given a few prompts. To try out the library, we don't need anything too crazy:

➜ ng new transloco-test
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? CSS

Now we'll need to navigate into the newly created transloco-test folder: cd transloco-test

From here we can use the ng add command to add Transloco to our application.
Note: By running the ng add Schematic, Transloco will automatically create files in our project to cover the initial set up process!

ng add @ngneat/transloco

After running this command we'll see some prompts from the library for the initial set up. For now, we will stick to the defaults:

? 🌍 Which languages do you need? en, es
? πŸš€ Are you working with server side rendering? No

We will also see that some files were created and updated: Ng Add Schematic Output

Let's take a quick look at these files.
tranloco.config.js - stores some basic configuration settings used by some additional Transloco tools.
src/assets/i18n/{en|es}.json - these are the files in which our translations are stored. Usually, one file per language supported. They are set up as key-value pairs, however, Transloco does support nested keys.
src/app/transloco/transloco-root.module.ts - this file sets up the Transloco module, config, transpiler and translation loader.

Let's take a closer look at this file.

@Injectable({ providedIn: "root" })
export class TranslocoHttpLoader implements TranslocoLoader {
  constructor(private http: HttpClient) {}

  getTranslation(lang: string) {
    // We can see here that the file names of our translations are important
    // They must match the available languages in our app
    return this.http.get<Translation>(`/assets/i18n/${lang}.json`);
  }
}

@NgModule({
  exports: [TranslocoModule],
  providers: [
    {
      provide: TRANSLOCO_CONFIG,
      useValue: translocoConfig({
        // These strings of available langs must match our translation file names
        availableLangs: ["en", "es"],
        defaultLang: "en",
        // Remove this option if your application doesn't support changing language in runtime.
        reRenderOnLangChange: true,
        prodMode: environment.production,
      }),
    },
    { provide: TRANSLOCO_LOADER, useClass: TranslocoHttpLoader },
  ],
})
export class TranslocoRootModule {}

This file stores the configuration and the translation loading strategy that Transloco will use to fetch the translations for our app.
It's important to note that the filenames of our translation files must match the array of availableLangs that we support in the app.

Adding Translations to our templates

Transloco provides multiple methods for fetching translations in our templates:

  • Pipe
  • Attribute Directive
  • Structural Directive

Let's look at how to do each in turn.

First, we will set up some translations in our en.json located in our assets/i18n folder:

{
  "title": "Transloco Test",
  "welcomeText": "Hello {{ name }}"
}

Notice that welcomeText contains {{ name }}: this is a dynamic value that we pass into the translation.

We will also up es.json. You can translate this yourself if you want, I'm just going to prefix the translations with ES- to show the differentiation between the translations:

{
  "title": "ES- Transloco Test",
  "welcomeText": "ES- Hello {{ name }}"
}

Open up app.component.html and delete all the content in the file and insert the following:

<div>
  <h1>{{ 'title' | transloco }}</h1>
  <h3>{{ 'welcomeText' | transloco: {name: 'World'} }}</h3>
</div>

Now if you run the app using ng serve you should see the following:
Translated Text

Awesome! Transloco was able to find our keys and successfully fetch the correct translations for it!

We can also use an attribute directive to achieve the same result:

<div>
  <h1 transloco="title"></h1>
  <h3 transloco="welcomeText" [translocoParams]="{name: 'World'}"></h3>
</div>

I personally feel like this approach works well when you aren't providing dynamic values to the translation and are just using transloco="title". With multiple params it could get messy.

The final option for performing translations in the template is to use a structural directive. My favourite method personally.

<div *transloco="let t">
  <h1>{{ t('title') }}</h1>
  <h3>{{ t('welcomeText', {name: 'World'}) }}</h3>
</div>

All three approaches will give the same output; however, there is a performance benefit to using the structural directive approach. It only uses one subscription to update the full template during language changes and change detection cycles.

We can also get translations in your TS files if we need them:

Open app.component.ts and inject TranslocoService into the constructor.


constructor(
    private readonly translocoService: TranslocoService
){}

ngOnInit() {
    // We can then use the service to fetch translations
    const example = this.translocoService.translate('welcomeText', {
      name: 'World',
    });
}

Supporting Runtime Language Change

One of the greatest features of Transloco is that it allows you to change the language of the translations at runtime. And it's super simple to do!

Open up the app.component.ts file and create a new method:

  switchLanguage() {
    if (this.translocoService.getActiveLang() === 'en') {
      this.translocoService.setActiveLang('es');
    } else {
      this.translocoService.setActiveLang('en');
    }
  }

Now, when this method is called the translations will be changed from en to es.

Open app.component.html and add a button to toggle the language:

<div>
  <h1>{{ "title" | transloco }}</h1>
  <h3>{{ "welcomeText" | transloco: { name: "World" } }}</h3>
</div>

<button (click)="switchLanguage()">Switch Language</button>

Now, when we click on the button we should see our translations update in real-time: Language Switching

Pluralization Support

It's fantastic being able to set translations and have them rendered on screen, but what about the following scenario?

{{ numberOfSeconds }} seconds remaining

If numberOfSeconds is 1, that should read 1 second remaining but it won't. It will always render 1 seconds remaining.

That's a problem! However, Transloco has an official plugin that adds MessageFormat support.

The MessageFormat Plugin can be easily added to our app and used to support pluralization and gender in our translations.

Let's see how to add it. First, we need to install two new packages:

npm i messageformat @ngneat/transloco-messageformat

Then we need to initialize it in our TranslocoRootModule, so open transloco/transloco-root.module.ts and add the following to the imports array:

@NgModule({
  imports: [
    TranslocoMessageFormatModule.init()
  ]
  ...
})
export class TranslocoRootModule {}

And voila! Support has been added. But how do we use it?

Let's implement a {{ numOfResults }} search results translation to see it being used.

Open assets/i18n/en.json and add the following key-translation pair:

{
    ...,
    "searchResults": "{numOfResults, plural, =0 {no results found} one {1 search result} other {# search results}}"
}

Now in our app.component.html add the following below the welcome text:

<p>{{ t('searchResults', numOfResults) }}</p>

Finally, in your app.component.ts set a new property called numOfResults:

export class AppComponent {
    numOfResults = 0;

    ...
}

Change the value of numOfResults to see the translation change appropriately!

MessageFormat Support

Final Regards

Hopefully, this article shows how easy it is to implement I18N support into our Angular apps with the help of Transloco! Transloco has so many more features and plugins available to make your i18n experience even nicer.

Two that I'd like to call out are:

Translation Flattening

Transloco supports nested keys:

{
  "aria": {
    "label": "My Translatable A11Y Label"
  }
}

To resolve this, it must loop through each layer of nesting to find the appropriate key. Transloco has a tool available which will flatten the file to:

{
  "aria.label": "My Translatable A11Y Label"
}

Allowing for an O(1) (Big O Notation) lookup. Here's a link to the docs on how to achieve it: https://ngneat.github.io/transloco/docs/tools/optimize

Monorepo Support

Transloco also has a tool to extract translations from libraries within a monorepo, or from npm packages, to allow for more control over where we place our translations, allowing them to be close to the files they are used in. Here is a link to the documentation surrounding the Scoped Library Extractor Tool

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

Incremental Hydration in Angular cover image

Incremental Hydration in Angular

Incremental Hydration in Angular Some time ago, I wrote a post about SSR finally becoming a first-class citizen in Angular. It turns out that the Angular team really treats SSR as a priority, and they have been working tirelessly to make SSR even better. As the previous blog post mentioned, full-page hydration was launched in Angular 16 and made stable in Angular 17, providing a great way to improve your Core Web Vitals. Another feature aimed to help you improve your INP and other Core Web Vitals was introduced in Angular 17: deferrable views. Using the @defer blocks allows you to reduce the initial bundle size and defer the loading of heavy components based on certain triggers, such as the section entering the viewport. Then, in September 2024, the smart folks at Angular figured out that they could build upon those two features, allowing you to mark parts of your application to be server-rendered dehydrated and then hydrate them incrementally when needed - hence incremental hydration. I’m sure you know what hydration is. In short, the server sends fully formed HTML to the client, ensuring that the user sees meaningful content as quickly as possible and once JavaScript is loaded on the client side, the framework will reconcile the rendered DOM with component logic, event handlers, and state - effectively hydrating the server-rendered content. But what exactly does "dehydrated" mean, you might ask? Here's what will happen when you mark a part of your application to be incrementally hydrated: 1. Server-Side Rendering (SSR): The content marked for incremental hydration is rendered on the server. 2. Skipped During Client-Side Bootstrapping: The dehydrated content is not initially hydrated or bootstrapped on the client, reducing initial load time. 3. Dehydrated State: The code for the dehydrated components is excluded from the initial client-side bundle, optimizing performance. 4. Hydration Triggers: The application listens for specified hydration conditions (e.g., on interaction, on viewport), defined with a hydrate trigger in the @defer block. 5. On-Demand Hydration: Once the hydration conditions are met, Angular downloads the necessary code and hydrates the components, allowing them to become interactive without layout shifts. How to Use Incremental Hydration Thanks to Mark Thompson, who recently hosted a feature showcase on incremental hydration, we can show some code. The first step is to enable incremental hydration in your Angular application's appConfig using the provideClientHydration provider function: ` Then, you can mark the components you want to be incrementally hydrated using the @defer block with a hydrate trigger: ` And that's it! You now have a component that will be server-rendered dehydrated and hydrated incrementally when it becomes visible to the user. But what if you want to hydrate the component on interaction or some other trigger? Or maybe you don't want to hydrate the component at all? The same triggers already supported in @defer blocks are available for hydration: - idle: Hydrate once the browser reaches an idle state. - viewport: Hydrate once the component enters the viewport. - interaction: Hydrate once the user interacts with the component through click or keydown triggers. - hover: Hydrate once the user hovers over the component. - immediate: Hydrate immediately when the component is rendered. - timer: Hydrate after a specified time delay. - when: Hydrate when a provided conditional expression is met. And on top of that, there's a new trigger available for hydration: - never: When used, the component will remain static and not hydrated. The never trigger is handy when you want to exclude a component from hydration altogether, making it a completely static part of the page. Personally, I'm very excited about this feature and can't wait to try it out. How about you?...

Using HttpClient in Modern Angular Applications cover image

Using HttpClient in Modern Angular Applications

Introduction With all the wonderful treats that the Angular team has given us during the recent "renaissance" era, many new developers are joining in on the fun. And one of the challenges they'll face at some point is how to call an API from your Angular application properly. Unfortunately, while searching for a guide on how to do this, they might stumble upon a lot of outdated information. Hence, this article should serve as a reliable guide on how to use the HttpClient in Angular >= 17. The Setup To make an HTTP request in Angular, you can take advantage of the HttpClient provided by the @angular/common/http package. To use it, you'll need to provide it. Here's how you can do that for the whole application using the bootstrapApplication function from @angular/platform-browser: ` With that, you should be good to go. You can now inject the HttpClient into any service or component in your application. Using HttpClient in Services Let's take a common example: You have a database object, say a Movie, and you want to implement CRUD operations on it. Typically, you'll want to create a service that provides methods for these operations. Let's call this service MovieService and create a skeleton for it with a method for getting all movies. ` Implementing the Method using HttpClient Let's assume we have a GraphQL API for our movies. We can implement our getAllMovies using HttpClient to make a request to fetch all movies. First, we will need to define a new type to represent the response from the API. This is especially important when you are using GraphQL, which may return a specific structure, such as: ` When working with a real API, you'll likely use some code generator to generate the types for the response from the GraphQL schema. But for the sake of this example, we'll create an interface to represent the response manually: ` Now, we can implement the getAllMovies method using HttpClient: ` > Note: The post method is used here because we are sending a request body. If you are making a GET request (e.g. to a REST API), you can use the get method instead. The getAllMovies method returns an Observable of MoviesListResponse. In this example, I have typed it explicitly to make it obvious at first glance, but you could also omit the type annotation, and TypeScript should infer it. > Note: While I'm excited about signals as much as the next guy, making HTTP requests is one of the typical async operations for which RxJS Observables are a perfect fit, making a great argument for RxJS still having a solid place in Angular alongside signals. Using the Service in a Component Now that we have our MovieService set up, we can use it as a component to fetch and display all movies. But first, let's create a Movie interface to represent the structure of a movie. Trust me, this will prevent many potential headaches down the line. Although using any at the beginning and implementing the types later is a valid approach in some cases, using data fetched from an API without validating the type will inevitably lead to bugs that are difficult to solve. ` Now, we can start implementing our standalone MoviesComponent: ` In this component, we are calling the getAllMovies method from the MovieService in the constructor to fetch all movies and assign them to the movies property which we will use to display the movies in the template. ` In this case, placing our code inside the constructor is safe because it doesn’t depend on any @Input(). If it were, our code would fail because the inputs aren’t initialized at time of instantiation. That's why it is sometimes recommended to place logic in ngOnInit instead. You could also put this call in an arbitrary method that is called e.g. on a button click. Another way to handle the subscription is to use the async pipe in the template. This way, Angular will automatically subscribe and unsubscribe from the observable for you and you won't have to assign the response to a property in the component. Due to the structure of the returned data, however, we'll need to use the RxJS map operator to extract the movies from the response. ` > Note using the pipe method to chain the map operator to the observable returned by getAllMovies. This is a common pattern when working with RxJS observables introduced in RxJs 5.5. If you see code that uses the map operator directly on the observable and wonder why it isn't working for you, it's likely using an older version of RxJS. Now, we can simply apply the async pipe in the template to subscribe to the observable and display the movies: ` Conclusion HttpClient in Angular is pretty straightforward, but with all the changes to RxJS and Angular in the past few years, it can pose a significant challenge if you're not super-experienced with those technologies and stumble upon outdated resources. Following this article, you should hopefully be able to implement your service using HttpClient to make requests to an API in your modern Angular application and avoid the struggle of copying outdated code snippets....

Going Reactive with RxJS cover image

Going Reactive with RxJS

RxJS is the perfect tool for implementing reactive programming paradigms to your software development. In general, software development handling errors gracefully is a fundamental piece of ensuring the integrity of the application as well as ensuring the best possible user experience. In this article, we will look at how we handle errors with RxJS and then look at how we can use RxJS to build a simple yet performant application. Handling Errors Our general approach to errors usually consists of us exclaiming "Oh no! What went wrong?" but it's something that is a common occurrence in all applications. The ability to manage errors well without disrupting the user's experience, while also providing accurate error logs to allow a full diagnosis of the cause of the error, is more important than ever. RxJS gives us the tools to do this job very well! Let's take a look at some basic error handling approaches with RxJS. Basic Error Handling The most basic way of detecting and reacting to an error that has occurred in an Observable stream provided to us by the .subscribe() method. ` Here we can set up two different pieces of logicβ€”one to handle non-error emission from the Observable and one to gracefully handle errors emitted by the Observable. We could use this to show a Notification Toast or Alert to inform the user that an error has occurred: ` This can help us minimize disruption for the user, giving them instant feedback that something _actually_ hasn't worked as appropriate rather than leaving them to guess. Composing Useful Errors Sometimes, however, we may have situations wherein we want to throw an error ourselves. For example, some data we received isn't quite correct, or maybe some validation checks failed. RxJS provides us with an operator that allows us to do just that. Let's take an example where we are receiving values from an API, but we encounter missing data that will cause other aspects of the app not to function correctly. ` If we receive a value from the Observable that doesn't contain an ID, we throw an error that we can handle gracefully. _NOTE: Using the throwError will stop any further Observable emissions from being received._ Advanced Error Handling We've learned that we can handle errors reactively to prevent too much disruption for the user. But what if we want to do multiple things when we receive an error or even do a retry? RxJS makes it super simple for us to retry errored Observables with their retry() operator. Therefore, to create an even cleaner error handling setup in RxJS, we can set up an error management solution that will receive any errors from the Observable, retry them in the hopes of a successful emission, and, failing that, handle the error gracefully. ` Once we reach an error, emitting the EMPTY observable will complete the Observable. The output of an error emission above is: ` Usage in Frontend Development RxJS can be used anywhere running JavaScript; however, I'd suggest that it's most predominately used in Angular codebases. Using RxJS correctly with Angular can massively increase the performance of your application, and also help you to maintain the Container-Presentational Component Pattern. Let's see a super simple Todo app in Angular to see how we can use RxJS effectively. Basic Todo App We will have two components in this app: the AppComponent and the ToDoComponent. Let's take a look at the ToDoComponent first: ` Pretty simple, right? It takes an item input and outputs an event when the delete button is clicked. It performs no real logic itself other than rendering the correct HTML. One thing to note is changeDetection: ChangeDetectionStrategy.OnPush. This tells the Angular Change Detection System that it should only attempt to re-render this component when the Input has changed. Doing this can increase performance _massively_ in Angular applications and _should_ always be applicable to _pure_ presentational components, as they should only be rendering data. Now, let's take a look at the AppComponent. ` This is a container component, and it's called this because it handles the logic relating to updating component state as well as handles or dispatches side effects. Let's take a look at some areas of interest: ` We create a basic local store to store our ToDo items; however, this could be done via a state management system or an API. We then set up our Observable, which will stream the value of our ToDo list to anyone who subscribes to it. You may now look over the code and begin to wonder where we have subscribed to items$. Angular provides a very convenient Pipe that handles this for us. We can see this in the template: ` In particular, it's the (items$ | async) this will take the latest value emitted from the Observable and provide it to the template. It does much more than this though. It also will manage the subscription for us, meaning when we destroy this container component, it will unsubscribe automatically for us, preventing unexpected outcomes. Using a pure pipe in Angular also has another performance benefit. It will only ever re-run the code in the Pipe if the input to the pipe changes. In our case, that would mean that item$ would need to change to a whole new Observable for the code in the async pipe to be executed again. We never have to change item$ as our values are then streamed through the Observable. Conclusion Hopefully, you have learned both about how to handle errors effectively as well put RxJS into practice into a real-world app that improves the overall performance of your application. You should also start to see the power that using RxJS effectively can bring!...

Understanding Sourcemaps: From Development to Production cover image

Understanding Sourcemaps: From Development to Production

What Are Sourcemaps? Modern web development involves transforming your source code before deploying it. We minify JavaScript to reduce file sizes, bundle multiple files together, transpile TypeScript to JavaScript, and convert modern syntax into browser-compatible code. These optimizations are essential for performance, but they create a significant problem: the code running in production does not look like the original code you wrote. Here's a simple example. Your original code might look like this: ` After minification, it becomes something like this: ` Now imagine trying to debug an error in that minified code. Which line threw the exception? What was the value of variable d? This is where sourcemaps come in. A sourcemap is a JSON file that contains a mapping between your transformed code and your original source files. When you open browser DevTools, the browser reads these mappings and reconstructs your original code, allowing you to debug with variable names, comments, and proper formatting intact. How Sourcemaps Work When you build your application with tools like Webpack, Vite, or Rollup, they can generate sourcemap files alongside your production bundles. A minified file references its sourcemap using a special comment at the end: ` The sourcemap file itself contains a JSON structure with several key fields: ` The mappings field uses an encoding format called VLQ (Variable Length Quantity) to map each position in the minified code back to its original location. The browser's DevTools use this information to show you the original code while you're debugging. Types of Sourcemaps Build tools support several variations of sourcemaps, each with different trade-offs: Inline sourcemaps: The entire mapping is embedded directly in your JavaScript file as a base64 encoded data URL. This increases file size significantly but simplifies deployment during development. ` External sourcemaps: A separate .map file that's referenced by the JavaScript bundle. This is the most common approach, as it keeps your production bundles lean since sourcemaps are only downloaded when DevTools is open. Hidden sourcemaps: External sourcemap files without any reference in the JavaScript bundle. These are useful when you want sourcemaps available for error tracking services like Sentry, but don't want to expose them to end users. Why Sourcemaps During development, sourcemaps are absolutely critical. They will help avoid having to guess where errors occur, making debugging much easier. Most modern build tools enable sourcemaps by default in development mode. Sourcemaps in Production Should you ship sourcemaps to production? It depends. While security by making your code more difficult to read is not real security, there's a legitimate argument that exposing your source code makes it easier for attackers to understand your application's internals. Sourcemaps can reveal internal API endpoints and routing logic, business logic, and algorithmic implementations, code comments that might contain developer notes or TODO items. Anyone with basic developer tools can reconstruct your entire codebase when sourcemaps are publicly accessible. While the Apple leak contained no credentials or secrets, it did expose their component architecture and implementation patterns. Additionally, code comments can inadvertently contain internal URLs, developer names, or company-specific information that could potentially be exploited by attackers. But that’s not all of it. On the other hand, services like Sentry can provide much more actionable error reports when they have access to sourcemaps. So you can understand exactly where errors happened. If a customer reports an issue, being able to see the actual error with proper context makes diagnosis significantly faster. If your security depends on keeping your frontend code secret, you have bigger problems. Any determined attacker can reverse engineer minified JavaScript. It just takes more time. Sourcemaps are only downloaded when DevTools is open, so shipping them to production doesn't affect load times or performance for end users. How to manage sourcemaps in production You don't have to choose between no sourcemaps and publicly accessible ones. For example, you can restrict access to sourcemaps with server configuration. You can make .map accessible from specific IP addresses. Additionally, tools like Sentry allow you to upload sourcemaps during your build process without making them publicly accessible. Then configure your build to generate sourcemaps without the reference comment, or use hidden sourcemaps. Sentry gets the mapping information it needs, but end users can't access the files. Learning from Apple's Incident Apple's sourcemap incident is a valuable reminder that even the largest tech companies can make deployment oversights. But it also highlights something important: the presence of sourcemaps wasn't actually a security vulnerability. This can be achieved by following good security practices. Never include sensitive data in client code. Developers got an interesting look at how Apple structures its Svelte codebase. The lesson is that you must be intentional about your deployment configuration. If you're going to include sourcemaps in production, make that decision deliberately after considering the trade-offs. And if you decide against using public sourcemaps, verify that your build process actually removes them. In this case, the public repo was quickly removed after Apple filed a DMCA takedown. (https://github.com/github/dmca/blob/master/2025/11/2025-11-05-apple.md) Making the Right Choice So what should you do with sourcemaps in your projects? For development: Always enable them. Use fast options, such as eval-source-map in Webpack or the default configuration in Vite. The debugging benefits far outweigh any downsides. For production: Consider your specific situation. But most importantly, make sure your sourcemaps don't accidentally expose secrets. Review your build output, check for hardcoded credentials, and ensure sensitive configurations stay on the backend where they belong. Conclusion Sourcemaps are powerful development tools that bridge the gap between the optimized code your users download and the readable code you write. They're essential for debugging and make error tracking more effective. The question of whether to include them in production doesn't have a unique answer. Whatever you decide, make it a deliberate choice. Review your build configuration. Verify that sourcemaps are handled the way you expect. And remember that proper frontend security doesn't come from hiding your code. Useful Resources * Source map specification - https://tc39.es/ecma426/ * What are sourcemaps - https://web.dev/articles/source-maps * VLQ implementation - https://github.com/Rich-Harris/vlq * Sentry sourcemaps - https://docs.sentry.io/platforms/javascript/sourcemaps/ * Apple DMCA takedown - https://github.com/github/dmca/blob/master/2025/11/2025-11-05-apple.md...

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co