<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: David Jiménez</title>
    <description>The latest articles on DEV Community by David Jiménez (@dubisdev).</description>
    <link>https://dev.to/dubisdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F694985%2F197ea556-be6d-48b9-abaf-9d3f049b18a7.png</url>
      <title>DEV Community: David Jiménez</title>
      <link>https://dev.to/dubisdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dubisdev"/>
    <language>en</language>
    <item>
      <title>Diseñando el Sistema de Ingesta de Datos para la Baliza V-16 de la DGT</title>
      <dc:creator>David Jiménez</dc:creator>
      <pubDate>Sun, 25 Jan 2026 17:31:48 +0000</pubDate>
      <link>https://dev.to/dubisdev/disenando-el-sistema-de-ingesta-de-datos-para-la-baliza-v-16-de-la-dgt-9fl</link>
      <guid>https://dev.to/dubisdev/disenando-el-sistema-de-ingesta-de-datos-para-la-baliza-v-16-de-la-dgt-9fl</guid>
      <description>&lt;p&gt;La Dirección General de Tráfico (DGT) de España ha establecido como obligatorio el uso de la baliza V-16 en carretera a partir de este 2026. Más allá de toda la polémica en cuanto a la utilidad de la medida, este post tiene por objetivo proponer un sistema que dé soporte a estos dispositivos IoT.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;⚠️ Nota: Este post es largo y 100% escrito a mano (0% IA). Si quieres saltar directamente a la arquitectura propuesta haz &lt;a href="https://dev.to/dubisdev/disenando-el-sistema-de-ingesta-de-datos-para-la-baliza-v-16-de-la-dgt-9fl#:~:text=El%20sistema%20queda%20dise%C3%B1ado%20finalmente%20de%20la%20siguiente%20manera%3A"&gt;click aquí&lt;/a&gt;. Aunque te recomiendo leerlo entero ;)&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Capítulo 0: Metodología
&lt;/h2&gt;

&lt;p&gt;Este post va a seguir una metodología de varios pasos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Entender el Problema y detectar limitaciones&lt;/li&gt;
&lt;li&gt;Identificar a los Principales Actores&lt;/li&gt;
&lt;li&gt;Definir Requisitos Funcionales&lt;/li&gt;
&lt;li&gt;Definir Requisitos No Funcionales&lt;/li&gt;
&lt;li&gt;Establecer una API de alto nivel (diagrama de flujo)&lt;/li&gt;
&lt;li&gt;Diseñar para los Requisitos Funcionales&lt;/li&gt;
&lt;li&gt;Ajustar para los Requisitos No Funcionales&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Estos puntos no se corresponden uno a uno con los capítulos pero me parece importante dejar esta intro para que se entienda la metodología de diseño que voy a seguir.&lt;/p&gt;




&lt;h2&gt;
  
  
  Capítulo 1: Los primeros requisitos.
&lt;/h2&gt;

&lt;p&gt;El equipo de la DGT 3.0 recibe un aviso: "A partir de 2026 tendrá que estar operativo el sistema para recibir eventos de la Baliza V-16. Todos los coches de España deberán disponer de una y hacer uso de ella en caso de emergencia en carretera."&lt;/p&gt;

&lt;p&gt;La información, aunque no es demasiado extensa, ya deja entrever algunos retos técnicos. Aquí algunas de las primeras preguntas y datos clave:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;¿Debemos conectar las balizas directamente a nuestro sistema?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esto lo discutiremos más adelante y veremos qué se ha decidido finalmente. Spoiler: NO&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;¿A qué volumen de usuarios debemos dar soporte?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Haciendo una búsqueda rápida, según datos de 2023 en España había 23 millones de automóviles asegurados. Poniéndonos en la peor situación, imaginemos que todos circulan todos los días y que tenemos una tasa diaria de incidencias del 0,1%. &lt;strong&gt;Eso serían 23.000 vehículos enviando señales durante el día&lt;/strong&gt;. Obviamente esto es una barbaridad, pero como primera aproximación nos podemos hacer a la idea de que &lt;strong&gt;el volumen de datos va a ser grande&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;¿Cómo de importante es la información y cómo de crítico es el sistema?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La seguridad en carretera es algo &lt;strong&gt;primordial&lt;/strong&gt;. Necesitamos un sistema robusto y con &lt;strong&gt;alta disponibilidad&lt;/strong&gt;. Perder un evento puede marcar la diferencia entre tener un coche señalizado y no tenerlo.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;¿De qué base tecnológica partimos?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sinceramente, no he encontrado mucho. Este ejemplo parte de la premisa de que la DGT dispone de un &lt;em&gt;cloud&lt;/em&gt; que nos permita desplegar microservicios. Como se trata del diseño del sistema, los detalles específicos sobre proveedores los dejaremos a un lado.&lt;/p&gt;




&lt;h2&gt;
  
  
  Capítulo 2: Empezando a Pensar el Sistema
&lt;/h2&gt;

&lt;p&gt;¿Por qué no conectar las balizas a la DGT directamente? La respuesta es otra pregunta: Si la API fuese abierta, ¿cómo asegurarías que un actor malintencionado no intenta engañarte y llenar el mapa de señales?&lt;/p&gt;

&lt;p&gt;Definitivamente esta opción es poco viable. Por eso la DGT ha habilitado la API de ingesta de datos solo para empresas verificadas previamente (y entiendo que serán además las que tengan balizas certificadas). De su propia documentación y especificación:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1ic4uzx5xg71u1wvzc0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1ic4uzx5xg71u1wvzc0.png" alt="Especificacion v16" width="800" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq77fteox6pfbzpq3mpho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq77fteox6pfbzpq3mpho.png" alt="Especificación v16" width="636" height="129"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Por supuesto, esta decisión tiene un impacto en las características del sistema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hemos simplificado la gestión de ataques DDoS, tenemos un sistema más sencillo y seguro.&lt;/li&gt;
&lt;li&gt;Pero hemos sacado de nuestro control una parte de la estabilidad del sistema. Si el cloud del proveedor de las balizas cae, no recibiremos eventos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Siguiente tema: cómo identificar las incidencias.&lt;/p&gt;

&lt;p&gt;Queremos saber qué incidencias tenemos en tiempo real y eso técnicamente implica saber qué balizas están activas. Una solución simple podría ser un sistema basado en 2 eventos: uno de encendido (start) y otro de apagado de la baliza (end)&lt;/p&gt;

&lt;p&gt;Pero, ¿qué pasaría si alguno se pierde? Podría haber faltas de coherencia: eventos de final que llegan sin haber empezado, eventos de inicio que nunca terminan... En este caso una solución es añadir un tercer evento, de tipo "ping", que se repita en el tiempo y que nos permita solventar posibles errores en la red, pérdidas de mensajes o incluso fallos del hardware de la baliza.&lt;/p&gt;

&lt;p&gt;Esta segunda solución nos permite establecer un Time-To-Live, un periodo de tiempo durante el cual asumimos que la baliza está activa, si pasado ese periodo la baliza no envía más mensajes, incluso sin haber enviado el evento de final, podemos asumir que la incidencia ha finalizado.&lt;/p&gt;

&lt;h3&gt;
  
  
  Un poco más de Matemáticas
&lt;/h3&gt;

&lt;p&gt;La documentación de la DGT establece que el formato del evento debe ser el siguiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"actionid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"detection_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020-02-24T07:17:27Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-3.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;40.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"device_event_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"device_event_type_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"information_quality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto son 189 Bytes.&lt;/p&gt;

&lt;p&gt;La baliza debe tener batería suficiente para media hora. Suponiendo un mensaje "ping" cada 30 segundos esto nos deja con 60 mensajes totales por cada incidencia.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nota del David del futuro: la especificación oficial establece este ping cada 100 segundos, pero tampoco es relevante.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tamaño Incidencia = 189 B * 60 = 11340 B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No es mucho. Continuando con nuestra suposición de 23.000 incidencias diarias, el volumen total diario asciende a 260 MB diarios. &lt;strong&gt;Anualmente: 94GB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No es una cifra astronómica, con la tecnología actual el almacenamiento no será un problema. Además, esto es el tamaño del mensaje JSON, en base de datos habrá menos duplicidades (las keys no se guardan, solo los valores).&lt;/p&gt;




&lt;h2&gt;
  
  
  Capítulo 3: Requisitos Funcionales
&lt;/h2&gt;

&lt;p&gt;Ahora que tenemos una idea de cómo hacer que esto funcione, toca establecer qué es lo que el sistema tiene que hacer.&lt;/p&gt;

&lt;p&gt;Tenemos identificados a nuestros dos principales usuarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proveedores de Datos V-16&lt;/li&gt;
&lt;li&gt;Consumidores de la plataforma DGT 3.0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79j8pe02o3kklxhigphy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79j8pe02o3kklxhigphy.png" alt="Usuarios de la plataforma" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El sistema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;debe permitir a los Proveedores de Datos V-16 iniciar sesión y recibir un token de acceso temporal.&lt;/li&gt;
&lt;li&gt;debe permitir a los Proveedores de Datos V-16 enviar los eventos de las diferentes balizas e informarles de la recepción del mismo.&lt;/li&gt;
&lt;li&gt;debe permitir a los Consumidores de la plataforma DGT 3.0 obtener los datos de incidencias en tiempo real y obtener actualizaciones sobre esos datos.&lt;/li&gt;
&lt;li&gt;debe permitir a los Consumidores de la plataforma DGT 3.0 la consulta de datos históricos de incidencias.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Requisitos Excluidos del Proyecto
&lt;/h3&gt;

&lt;p&gt;Para este ejemplo he dejado fuera el registro de las Entidades proveedoras de Datos V-16, dado que necesitan de una certificación manual y no es relevante para el problema real: el manejo de grandes volúmenes de mensajes en tiempo real.&lt;/p&gt;

&lt;p&gt;También queda fuera la gestión de usuarios de la plataforma DGT 3.0 (registro y login), dado que es un sistema que ya existe. Simplemente aparecerá en nuestros esquemas pero no profundizaré en él.&lt;/p&gt;




&lt;h2&gt;
  
  
  Capítulo 4: Requisitos No Funcionales
&lt;/h2&gt;

&lt;p&gt;Como ya hemos visto en el capítulo 2, necesitamos un sistema robusto y rápido, que refleje lo más fielmente posible la realidad de las carreteras.&lt;/p&gt;

&lt;p&gt;Por eso:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Las confirmaciones de recepción de eventos deben realizarse en el menor tiempo posible. Objetivo: &lt;strong&gt;&amp;lt;500 ms.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Las incidencias deben aparecer en la plataforma en menos de &lt;strong&gt;5 segundos&lt;/strong&gt; desde que se iniciaron.&lt;/li&gt;
&lt;li&gt;Disponibilidad del sistema: &lt;strong&gt;SLA 99,99%&lt;/strong&gt; (menos de 1 hora de downtime anual)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Capítulo 5: Diseño del sistema para cumplir con los requisitos funcionales.
&lt;/h2&gt;

&lt;p&gt;Antes de entrar al diagrama de arquitectura, no está de más pararse a plantear cómo serán las interacciones en el sistema. Para ello he preparado este diagrama de flujo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmcct3rg8fn0a54ri0d7f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmcct3rg8fn0a54ri0d7f.png" alt="Diagrama de flujo" width="742" height="672"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Básicamente tenemos a nuestros dos tipos de clientes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;V-16 Data Providers&lt;/li&gt;
&lt;li&gt;DGT 3.0 Data Consumers&lt;/li&gt;
&lt;li&gt;Y a nosotros mismos (la DGT 3.0)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Junto a las acciones e interacciones con nuestro sistema. Vamos a ir construyendo poco a poco el sistema.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 - El Login de Proveedores
&lt;/h3&gt;

&lt;p&gt;Este es sencillo. Para el Login usaremos un servicio preexistente de Users y Auth.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqefq1v1v4652es41v9vj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqefq1v1v4652es41v9vj.png" alt="Flujo del Caso de Uso Login de proveedores" width="521" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;De la documentación oficial de la DGT:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa37kcdd5xciht38jpkqs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa37kcdd5xciht38jpkqs.png" alt="Especificación del login" width="800" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como ves existe una especificación muy concreta, pero no entraré en ella para centrarme en el diseño del sistema.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 - La Ingesta de Eventos
&lt;/h3&gt;

&lt;p&gt;Para este segundo flujo vamos a disponer de un total de tres microservicios.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7du6yuyrm1i1srijubpn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7du6yuyrm1i1srijubpn.png" alt="Diagrama con los 3 microservicios" width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;El servicio "&lt;strong&gt;Event Validator &amp;amp; Ingestor&lt;/strong&gt;" validará el evento recibido y, en caso de ser correcto, lo enviará a una &lt;strong&gt;cola de mensajes&lt;/strong&gt; para ser consumido por los otros dos servicios. Esta validación será rápida ya que solo incluye el formato y pequeñas comprobaciones: &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgb3fimoqgejukzydgu1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgb3fimoqgejukzydgu1.png" alt="lista de comprobaciones de formato de los eventos" width="800" height="375"&gt;&lt;/a&gt; Dependiendo de si queremos devolver un error en caso de recibir alertas de un evento del que no hemos recibido su evento de inicio, podemos añadir una llamada interna a nuestro servicio de &lt;strong&gt;Device Presence&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;El servicio de "&lt;strong&gt;Device Presence&lt;/strong&gt;" se enfoca en los eventos activos. Hace uso de una base de datos key/value en memoria (por ejemplo Redis) muy rápida, perfecta para almacenar las incidencias activas y permitir su consulta con bajas latencias.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;El servicio de "&lt;strong&gt;Incidents History&lt;/strong&gt;" se enfoca en la visión histórica. En este caso he elegido una base de datos SQL para almacenar la información a largo plazo. La idea es permitir la consulta para datos estadísticos y de mejora de carreteras (por ejemplo detectando puntos calientes).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Además, al añadir el "Event Validator &amp;amp; Ingestor", he considerado conveniente añadir un API Gateway para exponer de manera unificada tanto este como el servicio de Usuarios. Esto además nos permitirá cumplir con los criterios de seguridad relativos a las IPs desde las que se accede a la plataforma.&lt;/p&gt;

&lt;p&gt;El uso de la cola de mensajería en este caso está justificado por varios motivos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nos permite enviar el evento desde el "Event Ingestor" a dos microservicios diferentes.&lt;/li&gt;
&lt;li&gt;Nos permite desacoplar los servicios y asegurar que no se pierda ningún evento incluso aunque alguno de los servicios de almacenamiento pueda llegar a caer o estar saturado.&lt;/li&gt;
&lt;li&gt;También da cumplimiento a ciertos requisitos no funcionales (lo veremos más adelante).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;De momento con esto habríamos cumplido con los requisitos del primer grupo de clientes, los proveedores de datos V-16.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3 - Información en tiempo Real para los usuarios de DGT 3.0
&lt;/h3&gt;

&lt;p&gt;Para la conexión de nuestros usuarios utilizaremos el servicio de Usuarios y Auth, y crearemos un servicio en exclusiva para el tiempo real "Real Time Incidents Service". ¿Por qué uno nuevo y no el "Device Presence Service"? Para mantener a este &lt;strong&gt;stateless&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;El servicio de Real Time va a mantener conexiones abiertas (Web-Sockets o Server Sent Events). Para evitar meter esta carga a nuestro servicio de almacenamiento, vamos a mantenerlos separados. El servicio de tiempo real pedirá al de almacenamiento las incidencias activas y escuchará de una cola los cambios publicados por el servicio de Device Presence Service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fji1yngawwxupng778x7w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fji1yngawwxupng778x7w.png" alt="Diagrama del sistema de real time" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Y con esto ya tendríamos la arquitectura completa cumpliendo con los requisitos funcionales:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17bp7r8vzp52dhde8j2s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17bp7r8vzp52dhde8j2s.png" alt="Arquitectura completa de requisitos funcionales" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Capítulo 6: Perfilando el Sistema para Cumplir con los Requisitos No Funcionales.
&lt;/h2&gt;

&lt;p&gt;Los vuelvo a traer aquí para refrescar la memoria:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Las confirmaciones de recepción de eventos deben realizarse en el menor tiempo posible. Objetivo: &lt;strong&gt;&amp;lt;500 ms.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Las incidencias deben aparecer en la plataforma en menos de &lt;strong&gt;5 segundos&lt;/strong&gt; desde que se iniciaron.&lt;/li&gt;
&lt;li&gt;Disponibilidad del sistema: &lt;strong&gt;SLA 99,99%&lt;/strong&gt; (menos de 1 hora de downtime anual)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para cumplir con nuestro &lt;strong&gt;primer requisito&lt;/strong&gt; ya tenemos casi todo. Al haber desacoplado el "Event Ingestor" del servicio de Almacenamiento, podemos responder rápidamente sin tener que esperar a guardar el evento.&lt;/p&gt;

&lt;p&gt;Como extra, podemos redundar el servicio y añadir un Balanceador de Carga (Load Balancer) para asegurar disponibilidad y poder hacer frente a incrementos estacionales, por ejemplo las operaciones entrada y salida de vacaciones.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs8cko4w6eng4igb6a5kh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs8cko4w6eng4igb6a5kh.png" alt="Detalle del servicio de ingesta" width="793" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Los dos últimos también los tenemos bastante encaminados. Al utilizar una base de datos clave-valor en memoria, las operaciones de lectura y escritura son muy rápidas. El sistema de colas además nos permite leer los eventos desde diferentes instancias del servicio.&lt;/p&gt;

&lt;p&gt;En general, podemos duplicar tanto los servicios como las bases de datos para asegurar la alta disponibilidad y el escalado en caso de ser necesario. Esto dejará a nuestras bases de datos con una configuración AP en referencia al teorema CAP (Availability + Partition Tolerance). Tendremos por tanto &lt;strong&gt;consistencia eventual&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;El sistema queda diseñado finalmente de la siguiente manera:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpc40cysuxu066le3bgsz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpc40cysuxu066le3bgsz.png" alt="Esquema Final del Sistema" width="800" height="631"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Capítulo 7: Últimos comentarios y puntos de mejora.
&lt;/h2&gt;

&lt;p&gt;Hay ciertos temas en los que no he profundizado, más que nada porque ya están definidos en la documentación de la DGT y por tanto no aportaban mucho valor. Un dato clave para entender más a fondo todo esto es el &lt;code&gt;actionid&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;Durante todo el ciclo de vida de la incidencia existe un identificador único para cada baliza e incidencia. Es decir, una vez se apaga la baliza, el siguiente mensaje tendrá un ID de incidencia nuevo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdlg5aoa84krocdieq74s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdlg5aoa84krocdieq74s.png" alt="Especificación técnica del action id" width="606" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Un último apunte. Externalizar la recepción desde las balizas no evita que los propios proveedores no puedan ser víctimas del ataque y por tanto nosotros también como receptores finales de los eventos. No me he centrado en ello pero se podría implementar un servicio de análisis de transacciones para detectar posible actividad inusual (por ejemplo mensajes con localizaciones que no corresponden con carreteras) y de esta forma asegurar la calidad de los datos.&lt;/p&gt;

&lt;p&gt;Y con esto voy terminando. No sé cómo estará implementado realmente el cloud de la DGT, sería interesante conocerlo. Sin embargo, desde fuera hay algunos detalles que me han llamado la atención en el diseño (ya más a bajo nivel) y para los que voy a dejar propuestas de mejora:&lt;/p&gt;

&lt;h3&gt;
  
  
  Inconsistencias en el Naming
&lt;/h3&gt;

&lt;p&gt;El primero de ellos es la consistencia (o falta de ella) en el nombrado de las variables:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmowv2iyy3im3aeeh6b3b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmowv2iyy3im3aeeh6b3b.png" alt="Tabla con los nombres de variables" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tenemos &lt;code&gt;idcompany&lt;/code&gt; y &lt;code&gt;actionid&lt;/code&gt;. ¿Por qué no mantener la coherencia y tener el &lt;code&gt;id&lt;/code&gt; delante o detrás en ambas?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;idcompany&lt;/code&gt; y &lt;code&gt;idaction&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;companyid&lt;/code&gt; y &lt;code&gt;actionid&lt;/code&gt; (mi favorita)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por otro lado, en estas no se separan las palabras, sin embargo en las últimas variables utilizan snake_case. ¿Por qué no algo como?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;action_id&lt;/code&gt; y &lt;code&gt;company_id&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Un nombre polémico
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;actionid&lt;/code&gt; no parece muy representativo. En su propia definición:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Identificador único del evento anonimizando al dispositivo de origen, desde la activación a la desactivación&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Quizá habría sido una mejor opción &lt;code&gt;incidence_id&lt;/code&gt; o &lt;code&gt;session_id&lt;/code&gt;. Algo que represente un poco mejor la idea de que este id debe ser único y constante durante el tiempo que dure la incidencia.&lt;/p&gt;

&lt;h3&gt;
  
  
  Un &lt;em&gt;endpoint&lt;/em&gt; cuestionable
&lt;/h3&gt;

&lt;p&gt;Okey, no hace falta que sea una API RESTful, pero mi comentario viene de aquí:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5uezmx8x6vmaed32kmmq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5uezmx8x6vmaed32kmmq.png" alt="Especificación del Endpoint" width="324" height="131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El método http ya es &lt;code&gt;POST&lt;/code&gt; me parece en cierto modo redundante volver a meterlo en el nombre del endpoint  (&lt;code&gt;postincidence&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Un dominio poco institucional
&lt;/h3&gt;

&lt;p&gt;Esto ya se ha comentado en algunas fuentes críticas con esta nueva medida, pero sí que es cierto que la API se ha desplegado en un dominio que no parece a primera vista de la DGT:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3gjfznhkh9c9v6v5mgq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3gjfznhkh9c9v6v5mgq.png" alt="Especificación del dominio de la DGT" width="616" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sí que es cierto que es un dominio compartido por algunos de los otros servicios de la DGT 3.0, pero quizá no habría estado mal haberlo implementado como subdominio bajo el dominio dgt.es.&lt;/p&gt;




&lt;h2&gt;
  
  
  Despedida
&lt;/h2&gt;

&lt;p&gt;Y ya estaría. Si te ha parecido interesante el post dale un like y comenta algo. Te aseguro que ha llevado un trabajo escribirlo y me encantará leer y discutir cualquier decisión de diseño. Hasta la próxima 🚀🫶&lt;/p&gt;




&lt;h2&gt;
  
  
  Recursos y Referencias
&lt;/h2&gt;

&lt;p&gt;Documentación API V-16:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dgt30-esp/Caso-de-uso-1" rel="noopener noreferrer"&gt;https://github.com/dgt30-esp/Caso-de-uso-1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Especificación Técnica de la baliza V-16&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.dgt.es/export/sites/web-DGT/.galleries/downloads/muevete-con-seguridad/tecnologia-e-innovacion/certificados-v16/SENAL-V-16_vf_ES.pdf" rel="noopener noreferrer"&gt;https://www.dgt.es/export/sites/web-DGT/.galleries/downloads/muevete-con-seguridad/tecnologia-e-innovacion/certificados-v16/SENAL-V-16_vf_ES.pdf&lt;/a&gt;&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>microservices</category>
      <category>dgt</category>
      <category>iot</category>
    </item>
    <item>
      <title>Tauri v2: Dos nuevos conceptos que debes conocer antes de actualizar tus apps a la nueva versión</title>
      <dc:creator>David Jiménez</dc:creator>
      <pubDate>Sat, 17 Aug 2024 12:27:41 +0000</pubDate>
      <link>https://dev.to/dubisdev/tauri-v2-dos-nuevos-conceptos-que-debes-conocer-antes-de-actualizar-tus-apps-a-la-nueva-version-2nn1</link>
      <guid>https://dev.to/dubisdev/tauri-v2-dos-nuevos-conceptos-que-debes-conocer-antes-de-actualizar-tus-apps-a-la-nueva-version-2nn1</guid>
      <description>&lt;p&gt;Tauri es un framework para crear aplicaciones multiplataforma que se presenta como la alternativa más ligera y segura a Electron. Gracias a que está escrito en Rust, permite crear aplicaciones más rápidas y ejecutables mucho más pequeños 🤏. Tras varios meses en versión beta, a principios de Agosto el equipo detrás de Tauri anunció &lt;a href="https://v2.tauri.app/blog/tauri-2-0-0-release-candidate/" rel="noopener noreferrer"&gt;la publicación de la Release Candidate de la versión 2 del framework&lt;/a&gt; y la muy próxima publicación de la versión estable. En este artículo veremos dos conceptos clave para entender esta nueva versión que viene cargada de novedades y con el foco puesto en la seguridad y la developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plugins
&lt;/h2&gt;

&lt;p&gt;Los plugins no son exactamente algo nuevo en Tauri. En la primera versión del framework ya contábamos con soporte para algunos plugins muy útiles (&lt;em&gt;single-instance, sql, log, ...&lt;/em&gt;), sin embargo, en esta nueva versión, Tauri ha movido muchas de sus APIs a plugins externos, lo que permite un mayor control sobre lo que se instala y usa, así como una disminución del tamaño de los ejecutables.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbq1ktikfqedbk20tvyx1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbq1ktikfqedbk20tvyx1.png" alt="Web de Tauri donde se ven los diferentes plugins disponibles en la versión 2" width="800" height="646"&gt;&lt;/a&gt;&lt;/p&gt;
Algunos de los nuevos plugins disponibles en la web de Tauri v2



&lt;p&gt;Además de haber una gran cantidad de &lt;a href="https://v2.tauri.app/plugin/#plugins" rel="noopener noreferrer"&gt;nuevos plugins disponibles&lt;/a&gt;, Tauri v2 incorpora un &lt;em&gt;nuevo comando&lt;/em&gt; que permite instalar el plugin (añadir a package.json, Cargo.toml, inicializarlo en el archivo main.rs...) de manera automática ✨. Por ejemplo, este sería el comando para instalar el plugin &lt;code&gt;deep-link&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run tauri add deep-link
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si aún así ninguno de estos plugins consigue lo que necesitas, la nueva versión de Tauri pone a los desarrolladores muy fácil el crear nuevas extensiones para el framework. 10/10 💫&lt;/p&gt;

&lt;h2&gt;
  
  
  Capabilities
&lt;/h2&gt;

&lt;p&gt;Por otro lado, Tauri sigue poniendo el foco en la seguridad en el desarrollo de aplicaciones. Por ello, en esta nueva versión ha migrado su antiguo sistema de permisos a un sistema que permite ser mucho más específico en el uso de las APIs que utilizamos.&lt;/p&gt;

&lt;p&gt;En la versión 2, el equipo de Tauri nos habla de &lt;a href="https://v2.tauri.app/reference/acl/capability/" rel="noopener noreferrer"&gt;&lt;strong&gt;Capabilities&lt;/strong&gt;&lt;/a&gt;: un sistema de permisos que, al igual que se hacía a través del archivo &lt;code&gt;tauri.config.json&lt;/code&gt;, permite definir qué APIs deben estar disponibles, pero también &lt;strong&gt;en qué plataforma&lt;/strong&gt;, &lt;strong&gt;en qué ventanas&lt;/strong&gt; concretas de la aplicación y &lt;strong&gt;sobre qué archivos&lt;/strong&gt; (en el caso de ciertas APIs con acceso al sistema de archivos). Estos permisos además se pueden definir en diferentes archivos, lo que permite organizar el código de una forma más visual e intuitiva según lo necesite el desarrollador.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnqgpmhmi60tg1icnyws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnqgpmhmi60tg1icnyws.png" alt="Archivo de Capabilities de Ejemplo" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;
Ejemplo de un Archivo de Capabilities




&lt;p&gt;Aunque seguramente ya te hayas hecho una idea, los permisos que comienzan por &lt;code&gt;core:&lt;/code&gt; hacen referencia a Capabilities nativas de Tauri, mientras que las que pertenecen a plugins no lo hacen. Esta es una novedad publicada en la Release Candidate y es muy útil de cara a diferenciar los tipos de permisos que manejamos.&lt;/p&gt;

&lt;p&gt;¿Qué te han parecido estos dos conceptos? ¿Ya has comenzado a migrar tus apps a las versión 2 de Tauri? Deja en comentarios qué otras novedades te parecen interesantes de la próxima versión de Tauri 🚀&lt;/p&gt;

</description>
      <category>tauri</category>
      <category>tauriapp</category>
      <category>español</category>
    </item>
    <item>
      <title>Creating Your First Tauri App with React: A Beginner's Guide</title>
      <dc:creator>David Jiménez</dc:creator>
      <pubDate>Wed, 05 Apr 2023 14:44:03 +0000</pubDate>
      <link>https://dev.to/dubisdev/creating-your-first-tauri-app-with-react-a-beginners-guide-3eb2</link>
      <guid>https://dev.to/dubisdev/creating-your-first-tauri-app-with-react-a-beginners-guide-3eb2</guid>
      <description>&lt;p&gt;Hello everyone, I'm excited to share with you a tutorial on how to create your first Tauri app with React. My name is David, and I've been creating multiple apps recently with Tauri (and I love it). Tauri is a lightweight and secure Rust framework for creating cross-platform desktop applications. It allows you to use web technologies such as HTML, CSS, and JavaScript/TypeScript to build desktop apps, and supports multiple platforms, including Windows, macOS, and Linux.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll go through the steps to create a Tauri app with React from scratch. I'll assume that you have some basic knowledge of React, although we are not going to do anything complicated. At the end of this tutorial, you'll have created a desktop app that will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CJR-IzUZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3g8i3krkddm8inpgnd6q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CJR-IzUZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3g8i3krkddm8inpgnd6q.png" alt="app screenshot" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rust: You can download Rust from &lt;a href="https://www.rust-lang.org/tools/install"&gt;https://www.rust-lang.org/tools/install&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Node.js: You can download Node.js from &lt;a href="https://nodejs.org/en/"&gt;https://nodejs.org/en/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;pnpm: (or your favourite package manager)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Creating the Project Structure with create-tauri-app
&lt;/h3&gt;

&lt;p&gt;Tauri provides a powerful cli tool to create projects from scratch. The first step is to create a new Tauri project using the create-tauri-app command. Open your terminal and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pnpm create tauri-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;NOTE: You can find the correct command for your package manager &lt;a href="https://tauri.app/v1/guides/getting-started/setup/"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The tool will ask you about the project technologies that you want to use. In my case:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9u31DV8T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gmj9788b7yyiavj5sge4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9u31DV8T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gmj9788b7yyiavj5sge4.png" alt="create-tauri-app cli options choosed" width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's follow the steps provided in the output:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;tldrawapp
&lt;span class="nv"&gt;$ &lt;/span&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WpLM9R59--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/74sax3m70uec1oi1d9cy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WpLM9R59--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/74sax3m70uec1oi1d9cy.png" alt="bash commands to install the needed dependencies" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this commands, we have installed the needed dependencies to work in our project.&lt;/p&gt;

&lt;p&gt;You can run &lt;code&gt;pnpm tauri dev&lt;/code&gt; to compile your project and see the template app working:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I-2Oo4Yv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jdupz0mb7hprxkeo546p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I-2Oo4Yv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jdupz0mb7hprxkeo546p.png" alt="capture of the boilerplate app working" width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before continuing, let's delete the generated code that we are not interested in. You can use VSCode and two extensions, &lt;code&gt;tauri-vscode&lt;/code&gt;, and &lt;code&gt;rust-analyzer&lt;/code&gt;, to do so.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KmK_aW5v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eqwmmqwffg8et8jdxs4x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KmK_aW5v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eqwmmqwffg8et8jdxs4x.png" alt="tauri-vscode screencapture" width="434" height="85"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--47L2xPET--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cxpqksvk902oc77co9nx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--47L2xPET--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cxpqksvk902oc77co9nx.png" alt="rust-analyzer screencapture" width="443" height="92"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The generated project has two important folders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src&lt;/code&gt;: for the frontend (HTML, JS, CSS)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;src-tauri&lt;/code&gt;: for the backend (rust)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kURnUn1k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tflkb7xzxpdrziz70uy1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kURnUn1k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tflkb7xzxpdrziz70uy1.png" alt="folder structure of the project" width="429" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll mainly work in &lt;code&gt;src&lt;/code&gt; but let's remove some boilerplate from &lt;code&gt;src-tauri&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;Remove some code to make &lt;code&gt;src-tauri/src/main.rs&lt;/code&gt; simpler. You don't need to do what we are doing, just remove unnecessary code to make it look like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Prevents additional console window on Windows in release, DO NOT REMOVE!!&lt;/span&gt;
&lt;span class="nd"&gt;#![cfg_attr(not(debug_assertions),&lt;/span&gt; &lt;span class="nd"&gt;windows_subsystem&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"windows"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;tauri&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tauri&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;generate_context!&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error while running tauri application"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now let's remove unnecessary code from the frontend: icons, default styles and some react code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete &lt;code&gt;src/styles.css&lt;/code&gt;, &lt;code&gt;src/App.css&lt;/code&gt;, the &lt;code&gt;src/assets&lt;/code&gt; folder and the svg files from &lt;code&gt;public&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;Remove the imports of the styles in the tsx files.&lt;/li&gt;
&lt;li&gt;Delete all the content of the &lt;code&gt;App&lt;/code&gt; component&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your file structure should look like this now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4He5OrbY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ao3xsf5m66feu5bl9dsn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4He5OrbY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ao3xsf5m66feu5bl9dsn.png" alt="src file structure" width="247" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the content in the files:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;App.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Welcome to Tauri!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;main.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The app should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JERYleeQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w37hkb4o0t7pim9s0j5b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JERYleeQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w37hkb4o0t7pim9s0j5b.png" alt="empty app screenshot" width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Install and Use TLDraw
&lt;/h3&gt;

&lt;p&gt;Next, we'll install the TLDraw library for drawing in our Tauri app. In your terminal, run the following command to install the package in the project:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pnpm add @tldraw/tldraw &lt;span class="nt"&gt;-E&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We can now import the editor component in our &lt;code&gt;App.tsx&lt;/code&gt; file and use it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;App.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Tldraw&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tldraw/tldraw&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Tldraw&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With this simple step we can see we have made a great advance in our app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Uo98bRV7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/te16q6m0egvlql3cbbj6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Uo98bRV7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/te16q6m0egvlql3cbbj6.png" alt="first version os the app with the tldraw editor" width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But it looks ugly for now. Let's add some styles! Create a new file in &lt;code&gt;src&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;styles.css&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;overscroll-behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Helvetica&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And then import it from &lt;code&gt;main.tsx&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;main.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./styles.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The app now looks ✨ amazing ✨ and ready to publish:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ok8GT6Jx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2zrq39922uew6a3yg51j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ok8GT6Jx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2zrq39922uew6a3yg51j.png" alt="app screenshot" width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: The app is missing some important styles, but I wanted to keep this simple and easy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Step 3: Set Up the Project for Creating the Executable
&lt;/h3&gt;

&lt;p&gt;Before we can create the executable for our app, we need to configure the Tauri settings in the &lt;code&gt;tauri.conf.json&lt;/code&gt; file. Open the file and modify the following configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the &lt;code&gt;package&lt;/code&gt; entry:

&lt;ul&gt;
&lt;li&gt;Change the &lt;code&gt;productName&lt;/code&gt; field as you want. &lt;/li&gt;
&lt;li&gt;Change the &lt;code&gt;version&lt;/code&gt; field to &lt;code&gt;../package.json&lt;/code&gt;: this will make Tauri use the version of the &lt;code&gt;package.json&lt;/code&gt; so you don't need to update it in both files.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;tauri&lt;/code&gt; entry:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;allowList&lt;/code&gt;: Tauri is a very secure environment for developing apps and it disables some web APIs by default. We must turn on some features for our app to work fine:&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;dialog&lt;/code&gt; API to enable all features (this will allow our app to use the &lt;code&gt;alert()&lt;/code&gt; and other related functions&lt;/li&gt;
&lt;li&gt;Change the &lt;code&gt;bundle&lt;/code&gt; entry to customize your app identifier&lt;/li&gt;
&lt;li&gt;Modify the &lt;code&gt;windows&lt;/code&gt; entry and change the app window title&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final configuration should look like this:&lt;br&gt;
&lt;code&gt;tauri.conf.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"beforeDevCommand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pnpm dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"beforeBuildCommand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pnpm build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"devPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:1420"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"distDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"../dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"withGlobalTauri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"productName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MyApp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"../package.json"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tauri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowlist"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dialog"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bundle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"icon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"icons/32x32.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"icons/128x128.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"icons/128x128@2x.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"icons/icon.icns"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"icons/icon.ico"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"identifier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my.tauri.app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"targets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"all"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"security"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"csp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"updater"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"windows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"fullscreen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"resizable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My fisrt Tauri App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;NOTE: keep in mind that if you are using a different package manager the first entries might be different.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Step 4: Build the executable
&lt;/h3&gt;

&lt;p&gt;Now that our app is ready, we can generate the executables with:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm tauri build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will generate a executable file for your OS that you can share with your friends.&lt;/p&gt;
&lt;h2&gt;
  
  
  Download the code
&lt;/h2&gt;

&lt;p&gt;All the code is available in this github repo :)&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/dubisdev"&gt;
        dubisdev
      &lt;/a&gt; / &lt;a href="https://github.com/dubisdev/tauri-tutorial-tldraw"&gt;
        tauri-tutorial-tldraw
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The repo containing the project with the TLDraw files
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Tauri + React Tutorial&lt;/h1&gt;
&lt;p&gt;This repo contains the code for my &lt;a href="https://dev.to/dubisdev/creating-your-first-tauri-app-with-react-a-beginners-guide-3eb2" rel="nofollow"&gt;Tauri + React Tutorial&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/dubisdev/tauri-tutorial-tldraw"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Outro
&lt;/h2&gt;

&lt;p&gt;Thanks for reading this tutorial on creating your first Tauri app with React! I hope you found it helpful. If you have any questions or comments, feel free to leave them below.&lt;/p&gt;

&lt;p&gt;If you enjoyed this tutorial, please consider supporting me by giving it a like, sharing it with your friends, or following me on my social media channels. I'll be sharing more tutorials, tips, and tricks on Tauri, React, and other web technologies that you won't want to miss!&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://github.com/dubisdev" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--fwH-OsyV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://avatars.githubusercontent.com/u/77246331%3Fv%3D4%3Fs%3D400" height="460" class="m-0" width="460"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://github.com/dubisdev" rel="noopener noreferrer" class="c-link"&gt;
          dubisdev (David Jiménez) · GitHub
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          Creating apps :)
📚 SSME Student at URJC 🔧 JavaScript &amp;amp; TypeScript developer 🧠 @AddTodoist  - dubisdev
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--GiYjWU4I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.githubassets.com/favicons/favicon.svg" width="32" height="32"&gt;
        github.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://dubis.dev" rel="noopener noreferrer"&gt;
      dubis.dev
    &lt;/a&gt;
&lt;/div&gt;



&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://twitter.com/@dubisdotdev" rel="noopener noreferrer"&gt;
      twitter.com
    &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>tauriapp</category>
      <category>react</category>
    </item>
    <item>
      <title>Cómo integrar Google Analytics en NextJS para cumplir con la Ley de Protección de Datos</title>
      <dc:creator>David Jiménez</dc:creator>
      <pubDate>Sat, 09 Oct 2021 20:14:32 +0000</pubDate>
      <link>https://dev.to/dubisdev/como-integrar-google-analytics-en-nextjs-para-cumplir-con-la-ley-de-proteccion-de-datos-4c1e</link>
      <guid>https://dev.to/dubisdev/como-integrar-google-analytics-en-nextjs-para-cumplir-con-la-ley-de-proteccion-de-datos-4c1e</guid>
      <description>&lt;h2&gt;
  
  
  ¿Qué vamos a hacer?
&lt;/h2&gt;

&lt;p&gt;En este post configuraremos Google Analytics en una página web creada con NextJS, teniendo en cuenta la autorización del usuario para la recogida de datos y el uso de cookies.&lt;/p&gt;




&lt;h2&gt;
  
  
  Requisitos previos
&lt;/h2&gt;

&lt;p&gt;Antes de comenzar con el código, necesitarás tener acceso a los siguientes recursos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Archivo &lt;a href="https://nextjs.org/docs/advanced-features/custom-document" rel="noopener noreferrer"&gt;_document.js&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Archivo &lt;a href="https://nextjs.org/docs/advanced-features/custom-app" rel="noopener noreferrer"&gt;_app.js&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Puedes consultar la &lt;a href="https://nextjs.org/docs" rel="noopener noreferrer"&gt;documentación oficial&lt;/a&gt; para saber qué son y cómo configurarlos en tu proyecto.&lt;/p&gt;

&lt;p&gt;También deberás tener acceso a una cuenta de &lt;a href="https://analytics.google.com/analytics/web/" rel="noopener noreferrer"&gt;Google analytics&lt;/a&gt; y deberás crear un nuevo proyecto de GA y obtener la &lt;a href="https://support.google.com/analytics/answer/7372977" rel="noopener noreferrer"&gt;ID de seguimiento&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Configurar Google Analytics en la web
&lt;/h2&gt;



&lt;h3&gt;
  
  
  Configurando los scripts de Google Analytics
&lt;/h3&gt;

&lt;p&gt;Lo primero que deberemos hacer es configurar los scripts de GA en el archivo &lt;code&gt;_document.js&lt;/code&gt;. Estos scripts son los que se encargan de configurar la instancia de GA a nivel global en nuestro sitio web. Para ello abrimos el archivo &lt;code&gt;_document.js&lt;/code&gt; y añadimos las siguientes líneas de código en el elemento &lt;code&gt;head&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`https://www.googletagmanager.com/gtag/js?id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
    &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;`
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}

        gtag('consent', 'update', {
            'analytics_storage': 'granted'
        });

        gtag('js', new Date());

        gtag('config', '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;', {
            page_path: window.location.pathname,
        });
        `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;El archivo final debería quedar similar a esto:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextScript&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/document&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyDocument&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Document&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getInitialProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;initialProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInitialProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;initialProps&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`https://www.googletagmanager.com/gtag/js?id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;
                        &lt;span class="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
                            &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;`
                            window.dataLayer = window.dataLayer || [];
                            function gtag(){dataLayer.push(arguments);}

                            gtag('consent', 'update', {
                                'analytics_storage': 'granted'
                            });

                            gtag('js', new Date());

                            gtag('config', '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;', {
                                page_path: window.location.pathname,
                            });
                            `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;}}&lt;/span&gt;
                    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Head&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Main&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NextScript&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/body&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Html&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;MyDocument&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Como ves, estamos utilizando una variable de entorno, que configuraremos más adelante, donde se almacenará el ID de seguimiento de Google Analytics.&lt;/p&gt;

&lt;p&gt;Además, para poder detectar los diferentes eventos que ocurren en la página, es necesario añadir las siguientes líneas al archivo &lt;code&gt;_app.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/router&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;//dentro del componente App&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleRouteChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;page_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;routeChangeComplete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleRouteChange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;off&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;routeChangeComplete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleRouteChange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;El archivo final quedará similar a esto:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../styles/globals.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/router&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageProps&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleRouteChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;page_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;routeChangeComplete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleRouteChange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;off&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;routeChangeComplete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleRouteChange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pidiendo permiso al usuario: renderizado condicional de los scripts de GA.
&lt;/h3&gt;

&lt;p&gt;Con esto ya tenemos Google Analytics funcionando en nuestro sitio web. Pero ahora debemos tener en cuenta que Google Analtics solo debe ser utilizado tras el consentimineto del usuario, por lo que procederemos a añadir los elementos necesarios para pedir ese permiso.&lt;/p&gt;

&lt;p&gt;El primer cambio que debemos realizar es &lt;strong&gt;configurar los scripts de analytics para que no se carguen automáticamente&lt;/strong&gt;. Para ello volveremos al archivo &lt;code&gt;_document.js&lt;/code&gt; y añadiremos un condicional a nuestro código:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;
    &lt;span class="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
        &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}

            //configuramos el permiso como denegado en primera instancia
            gtag('consent', 'default', {
                'analytics_storage': 'denied'
            });

            gtag('js', new Date());

            //comprobamos el consentimiento
            if( consentimiento ) {
                gtag('consent', 'update', {
                'analytics_storage': 'granted'
                });
            }

            gtag('config', '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;', {
                page_path: window.location.pathname,
            });
        `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Ahora no se cargan automáticamente los scripts sino que dependerá del consentimiento del usuario.&lt;/p&gt;

&lt;p&gt;Es el momento de preguntar al usuario si desea o no que realicemos seguimiento de sus datos. Para ello, podemos utilizar algún componente de React ya existente que nos permita preguntar al usuario y almacenar sus preferencias en una &lt;em&gt;cookie de preferencias&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;En este caso utilizaremos la librería &lt;a href="https://www.npmjs.com/package/react-cookie-consent" rel="noopener noreferrer"&gt;react-cookie-consent&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Podemos instalarla a través de la consola en nuestro proyecto:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;react-cookie-consent

&lt;span class="c"&gt;# o utilizando yarn&lt;/span&gt;

yarn add react-cookie-consent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Una vez instalado debemos añadir el componente de forma global, de manera que, entre donde entre el usuario, se le pregunte por sus preferencias, en caso de no tener un registro previo de las mismas. Este componente se encarga automáticamente de comprobar si ya hay una cookie previa de preferencias por lo que será muy facil implementar el sistema.&lt;/p&gt;

&lt;p&gt;Para añadirlo de forma global lo añadimos al componente &lt;code&gt;App&lt;/code&gt; de nuestra web:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../styles/globals.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/router&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CookieConsent&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-cookie-consent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pageProps&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleRouteChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;page_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;routeChangeComplete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleRouteChange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;off&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;routeChangeComplete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleRouteChange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;pageProps&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CookieConsent&lt;/span&gt;
            &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bottom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;buttonText&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sí, utilizar cookies.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;onAccept&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
            &lt;span class="nx"&gt;cookieName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CookieConsent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;expires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;enableDeclineButton&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="nx"&gt;declineButtonText&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No, no utilizar cookies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nx"&gt;Poner&lt;/span&gt; &lt;span class="nx"&gt;aquí&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="nx"&gt;mensaje&lt;/span&gt; &lt;span class="nx"&gt;sobre&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="nx"&gt;uso&lt;/span&gt; &lt;span class="nx"&gt;de&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#enlace_hacia_politica_de_cookies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Política&lt;/span&gt; &lt;span class="nx"&gt;de&lt;/span&gt; &lt;span class="nx"&gt;Cookies&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;.
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CookieConsent&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;Puedes cosultar el repo del componente para configurarlo a tu gusto.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ya tenemos la capacidad de crear una cookie de preferencias y de preguntarle al usuario qué es lo que quiere. Ahora debemos volver al archivo &lt;code&gt;_document.js&lt;/code&gt; e introducir la función que nos devuelva el valor de la cookie de preferencias, para saber si debemos cargar el script de rastreo o no.&lt;/p&gt;

&lt;p&gt;Añadimos lo siguiente en el archivo &lt;code&gt;_document.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;
    &lt;span class="nx"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
        &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        //this defaults to denying
        gtag('consent', 'default', {
            'analytics_storage': 'denied'
        });

        gtag('js', new Date());

        //este función es la que nos devuelve el valor de la cookie de preferencias
        function getCookie() {
            const value = "; " + document.cookie;
            const parts = value.split("; CookieConsent=");
            if (parts.length === 2) return parts.pop().split(';').shift();
        }

        //únicamente si el valor es true, se cargan los scripts de Google Analytics.
        if(getCookie() === "true"){
            gtag('consent', 'update', {
                'analytics_storage': 'granted'
            });
        }

        gtag('config', '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_ANALYTICS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;', {
            page_path: window.location.pathname,
        });
        `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Con esto ya tendremos todo funcionando 🚀. Ahora deberás hacer un deploy en Vercel y &lt;a href="https://vercel.com/docs/environment-variables" rel="noopener noreferrer"&gt;configurar la variable de entorno&lt;/a&gt; &lt;code&gt;GOOGLE_ANALYTICS&lt;/code&gt; con el valor del ID de rastreo de tu sitio web.&lt;br&gt;
Tras añadir la variable de entorno deberás de hacer otro despliegue para que estas san tenidas en cuenta en tu sitio web.&lt;/p&gt;


&lt;h2&gt;
  
  
  Código completo
&lt;/h2&gt;

&lt;p&gt;Puedes ver todo el código en el siguiente repositorio:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/dubisdev" rel="noopener noreferrer"&gt;
        dubisdev
      &lt;/a&gt; / &lt;a href="https://github.com/dubisdev/nextjs-analytics-rgpd" rel="noopener noreferrer"&gt;
        nextjs-analytics-rgpd
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;This is a &lt;a href="https://nextjs.org/" rel="nofollow noopener noreferrer"&gt;Next.js&lt;/a&gt; project bootstrapped with &lt;a href="https://github.com/vercel/next.js/tree/canary/packages/create-next-app" rel="noopener noreferrer"&gt;&lt;code&gt;create-next-app&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting Started&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;First, run the development server:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run dev
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; or&lt;/span&gt;
yarn dev&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Open &lt;a href="http://localhost:3000" rel="nofollow noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; with your browser to see the result.&lt;/p&gt;
&lt;p&gt;You can start editing the page by modifying &lt;code&gt;pages/index.js&lt;/code&gt;. The page auto-updates as you edit the file.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://nextjs.org/docs/api-routes/introduction" rel="nofollow noopener noreferrer"&gt;API routes&lt;/a&gt; can be accessed on &lt;a href="http://localhost:3000/api/hello" rel="nofollow noopener noreferrer"&gt;http://localhost:3000/api/hello&lt;/a&gt;. This endpoint can be edited in &lt;code&gt;pages/api/hello.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;pages/api&lt;/code&gt; directory is mapped to &lt;code&gt;/api/*&lt;/code&gt;. Files in this directory are treated as &lt;a href="https://nextjs.org/docs/api-routes/introduction" rel="nofollow noopener noreferrer"&gt;API routes&lt;/a&gt; instead of React pages.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Learn More&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;To learn more about Next.js, take a look at the following resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/docs" rel="nofollow noopener noreferrer"&gt;Next.js Documentation&lt;/a&gt; - learn about Next.js features and API.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/learn" rel="nofollow noopener noreferrer"&gt;Learn Next.js&lt;/a&gt; - an interactive Next.js tutorial.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can check out &lt;a href="https://github.com/vercel/next.js/" rel="noopener noreferrer"&gt;the Next.js GitHub repository&lt;/a&gt; - your feedback and contributions are welcome!&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Deploy on Vercel&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;The easiest way to deploy your Next.js app is to use the &lt;a href="https://vercel.com/new?utm_medium=default-template&amp;amp;filter=next.js&amp;amp;utm_source=create-next-app&amp;amp;utm_campaign=create-next-app-readme" rel="nofollow noopener noreferrer"&gt;Vercel Platform&lt;/a&gt; from…&lt;/p&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/dubisdev/nextjs-analytics-rgpd" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;





&lt;h3&gt;
  
  
  Crear una página de Política de Cookies
&lt;/h3&gt;

&lt;p&gt;¡Espera un momento! Todavía queda un paso importante: crear una página donde expliques el uso que se le da a las cookies y las instrucciones para revocar los permisos y/o borrar los datos en caso de ser necesario.&lt;/p&gt;

&lt;p&gt;Como idea, puedes echarle un vistazo a la &lt;a href="https://www.aepd.es/sites/default/files/2020-07/guia-cookies.pdf" rel="noopener noreferrer"&gt;publicación de la Agencia Estatal de Protección de Datos&lt;/a&gt; para saber qué debe incluir.&lt;/p&gt;






&lt;h2&gt;
  
  
  Exención de responsabilidades
&lt;/h2&gt;

&lt;p&gt;Este post contiene información &lt;strong&gt;general&lt;/strong&gt; sobre la configuración de Google Analytics y el cumplimiento con la legislación vigente de España al día de la fecha. Los &lt;strong&gt;requisitos legales podrían ser diferentes&lt;/strong&gt; atendiendo al tipo de empresa, localización y finalidad de la recogida de datos, por lo que es importante que se revise y consulte con una persona experta las necesidades legales de cada sitio web.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>analytics</category>
      <category>privacy</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Connecting to MongoDB from ESB Mule</title>
      <dc:creator>David Jiménez</dc:creator>
      <pubDate>Sat, 09 Oct 2021 19:59:48 +0000</pubDate>
      <link>https://dev.to/dubisdev/connecting-to-mongodb-from-esb-mule-4pa5</link>
      <guid>https://dev.to/dubisdev/connecting-to-mongodb-from-esb-mule-4pa5</guid>
      <description>&lt;p&gt;I recently discovered Anypoint Studio and the first question that came up to me was: how can I access my MongoDB cluster?&lt;br&gt;
This is the fastest solution I have found :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the Mule Project
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbbef2lvc0hqxo0446at.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbbef2lvc0hqxo0446at.png" alt="image" width="600" height="234"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Configure an HTTP Listener
&lt;/h2&gt;

&lt;p&gt;With the sole purpose of being able to test our project later, I have added and configured an HTTP Listener:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbjkb4v6nydd36eghelf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbjkb4v6nydd36eghelf.png" alt="image" width="611" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Install and configure the MongoDB connector
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the Mule Palette view and select &lt;code&gt;Search in Exchange&lt;/code&gt;.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwlsjkwnmsagyz9rs9l0e.png" alt="image" width="177" height="129"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the search box type "mongo" and search "MongoDB Connector" in the Available Modules tab. Then select it and click &lt;code&gt;add&lt;/code&gt; and &lt;code&gt;finish&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz43p1ysgha8p1pvnst71.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz43p1ysgha8p1pvnst71.png" alt="image" width="660" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Make sure you choose the 6.3 version or higher.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigate to &lt;code&gt;Global Elements&lt;/code&gt; tab.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;code&gt;Create&lt;/code&gt; &amp;gt; &lt;code&gt;Connector Configuration&lt;/code&gt; &amp;gt; MongoDB config&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvx0w4tuvdimh6bq4793c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvx0w4tuvdimh6bq4793c.png" alt="image" width="228" height="176"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the &lt;code&gt;General&lt;/code&gt; tab select "Connection string" as connection option.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4khoykehn369fa1aelj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4khoykehn369fa1aelj.png" alt="image" width="264" height="91"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some libraries must be installed. Just click &lt;code&gt;Configure...&lt;/code&gt; &amp;gt; &lt;code&gt;Add Recommended Libraries&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F26hmug5ufbrhw02o7tcb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F26hmug5ufbrhw02o7tcb.png" alt="image" width="719" height="79"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11noxqdgzn4ny883l2cp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11noxqdgzn4ny883l2cp.png" alt="image" width="580" height="60"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we must include the connection string from our MongoDB atlas clusters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get your Connection String
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to your MongoDB Atlas project&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select connect&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuggzayu9thrabcvrx1oz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuggzayu9thrabcvrx1oz.png" alt="image" width="415" height="359"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose "Connect your application" and select &lt;code&gt;Java&lt;/code&gt; as your application language (this is just for getting the connection string).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then copy the connection string and follow the MongoDB instructions (&lt;em&gt;Replace password...&lt;/em&gt;)&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7czt8xdp43n8ky0retn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7czt8xdp43n8ky0retn.png" alt="image" width="749" height="142"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Paste it to the Mule Connection string textbox...&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pxans8bclp2rc4evvl9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pxans8bclp2rc4evvl9.png" alt="image" width="569" height="59"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;code&gt;Test Connection&lt;/code&gt; to test if alll is working well&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtw4789vo7yveaeuy9cv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmtw4789vo7yveaeuy9cv.png" alt="image" width="576" height="218"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;[If you are having issues check the project allowed IPs and the database allowed users in your MongoDB settings]&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it!
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;From the Mule pallete add &lt;code&gt;Create Collection&lt;/code&gt; and &lt;code&gt;Insert document&lt;/code&gt; elements and select as Connector_Configuration the config we have just created.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure both elements with the collection name and the document you want to insert...&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9l8flk3mvd26g40rnfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9l8flk3mvd26g40rnfs.png" alt="image" width="582" height="635"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the project, go to the HTTP listener url and then to your MongoDB collection list...&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3fwonp0gkxuoemwnlypz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3fwonp0gkxuoemwnlypz.png" alt="image" width="384" height="133"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvctorr09kdwbgg9dqle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvctorr09kdwbgg9dqle.png" alt="image" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's all!  I hope this information is useful for your projects :)&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>anypoint</category>
      <category>esb</category>
    </item>
  </channel>
</rss>
