diff --git a/blog/ca/4_enginyeria-inversa-del-ecommerce-de-nike.mdx b/blog/ca/4_enginyeria-inversa-del-ecommerce-de-nike.mdx
new file mode 100644
index 0000000..a5efd24
--- /dev/null
+++ b/blog/ca/4_enginyeria-inversa-del-ecommerce-de-nike.mdx
@@ -0,0 +1,1136 @@
+---
+title: Enginyeria inversa del ecommerce de Nike amb només el navegador
+description: Article sobre les tecnologies que s'utilitzen a l'ecommerce de nike
+image: /assets/blog/descubrint-el-ecommerce-de-nike/cover-image.jpg
+published: true
+publishedDate: 2023-12-01
+authors: eudago
+seo:
+ metatitle: >
+ Enginyeria inversa del ecommerce de Nike amb només el navegador
+ metadescription: >
+ Article sobre les tecnologies que s'utilitzen a l'ecommerce de nike
+ image: >
+ /assets/blog/descubrint-el-ecommerce-de-nike/cover-image.jpg
+---
+
+El proposit d'aquest article es veure fins al punt que es pugui quines tecnologies utilitza i com les utiltiza un ecommerce "gran" com en aquest cas es el de nike i fer-ho utilitzant les developer tools del navegador.
+
+Primer de tot si inspeccionem la pàgina podem veure que utilitza el framework [Next.js](https://nextjs.org/),
+
+```html {1,3-4}
+
` element with the ID \_\_next can be found, which is often featured in one of the primary showcases on the Next.js official website itself.
+
+
+
+
+
+
Within the "Sources" tab of the browser's dev tools, we can observe the presence of the "folder" webpack://. This folder represents a virtual directory that allows us to view the original files. It's made possible due to the enabled source maps option in the webpack configuration (which, it's worth mentioning, is a somewhat uncommon occurrence).
+
+
+ We can also locate the webpack folder within other directories, such as _N_E, analytics-client, privacy-consent, privacy-Core, and WebShellClient, among others. This greatly simplifies our task when examining the content, as we won't have to deal with minified or obfuscated code.
+
+
+
+
+
+We can also utilize the [react devtools](https://react.dev/learn/react-developer-tools) to inspect the props and states of the components rendered on the page:
+
+![reat-devtocols](/assets/blog/descubrint-el-ecommerce-de-nike/react-devtools.PNG)
+
+Upon analysis, it's evident that they are employing Redux for managing the application state, which gets loaded into the initial component of the page. Here's a helpful tutorial on using Redux with Next.js.
+
+Within the initial page component, we can also identify the routes used in Next.js. For instance, the home route is:
+
+```json
+isSsr:true
+query: {
+ "page": [
+ "es",
+ "ca"
+ ]
+}
+route: "/[[...page]]"
+```
+
+Instead to the url of the offers with path **/es/ca/w/ofertes-3yaep**:
+
+```json
+isSsr:true
+query: {
+ country: "es",
+ language: "ca",
+ slug: ["ofertes-3yaep"]
+}
+route:"/[country]/[language]/w/[[...slug]]"
+```
+
+Or a product with path **/es/ca/t/dri-fit-fitness-shirt-men-v75KxD/AR6029-063**:
+
+```json
+isSsr:true
+query: {
+ slug: "dri-fit-samarreta-de-fitnes-home-v75KxD",
+ styleColor: "AR6029-063",
+}
+route:"/t/[slug]/[styleColor]"
+```
+
+One of the things we can extract from the properties received by the first component is the type of rendering they are using with Next.js.
+
+```json
+__N_SSP: true
+__N_SSG: undefined
+```
+
+We can observe that these pages are rendered using server-side rendering ([SSR](https://nextjs.org/docs/pages/building-your-application/rendering/static-site-generation)), and within server-side rendering, they do not utilize server-side generation (SSG). Instead, the pages are generated using getServerSideProps ([SSP](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props)). This is likely due to the volume of products they have and the need to keep the pages more up-to-date.
+
+However, the profile page (/es/ca/member/profile/) does utilize SSG, although the relevant content is actually loaded using client-side rendering (CSR).
+
+```json
+__N_SSG: true
+route:"/[country]/[language]/member/profile"
+```
+
+Certainly! We can analyze the cache of the different pages by inspecting the `Cache-Control` headers in the response of the initial request:
+
+
+
+
+ URL |
+ Cache-Control |
+
+
+
+
+ /es/ca/ |
+ max-age=646 |
+
+
+ /es/ca/w/ofertes-3yaep |
+ max-age=287 |
+
+
+ /es/ca/t/dri-fit-samarreta-de-fitnes-home-v75KxD/AR6029-063 |
+ max-age=899 |
+
+
+
+
+From what we can see, the home page refreshes every 10 minutes, the offers update every 5 minutes, and the products refresh every 15 minutes. However, delving deeper into caching is something we might reserve for a future article.
+
+Looking into the requests, one of the response headers is **Akamai-Grn**. If we search for Akamai in the code or directly on Google, we can find that it relates to various services offered by [Akami](https://en.wikipedia.org/wiki/Akamai_Technologies), such as cybersecurity, cloud services, or content delivery network (CDN), among many others.
+
+```js
+import { useRef } from 'react';
+import { useQuery } from 'react-query';
+import { fetchClient } from '@nike/fetch-client';
+import { getExperimentDriver } from '@nike/ux-tread-optimizely';
+import useOptimizelyEnabled from './use-optimizely-enabled';
+
+export const PROD_OPTIMIZELY_URL = `https://${process.env.NEXT_PUBLIC_HOST_NAME}/assets/vendor/optimizely/prod/5aUCtZvFuDLjvdqdf2w53M.json`;
+export const DEV_OPTIMIZELY_URL = `https://assets.commerce.nikecloud.com/vendor/optimizely/test/3srgubLSVD2VBXqgkJtCRh.json`; // NOTE - we need to use this for now until akamai is fixed
+export const OPTIMIZELY_ERROR_MESSAGE = 'Error fetching optimizely datafile';
+
+const useOptimizelyDataFile = () => {
+ const optimizelyEnabled = useOptimizelyEnabled();
+ const optimizelyClient = useRef();
+ const isProd = process.env.NODE_ENV === 'production';
+ const optimizelyUrl = isProd ? PROD_OPTIMIZELY_URL : DEV_OPTIMIZELY_URL;
+
+ const { data } = useQuery(
+ optimizelyUrl,
+ () =>
+ fetchClient(optimizelyUrl, { method: 'GET' }, OPTIMIZELY_ERROR_MESSAGE),
+ { enabled: optimizelyEnabled }
+ );
+
+ if (data) {
+ optimizelyClient.current = getExperimentDriver(data);
+ }
+
+ return { datafile: data, optimizelyClient: optimizelyClient };
+};
+
+export default useOptimizelyDataFile;
+```
+
+In this case it appears to be a CDN service.
+
+## Design system
+
+If we delve a bit deeper to answer the question of how they manage styles for React components, it seems that some of the components utilize [Emotion](https://emotion.sh/docs/introduction), a CSS-in-JS library.
+
+```js
+import styled from '@emotion/styled';
+```
+
+Aside from that, we can see various packages that reference a design system like **@nike/nike-design-system-components** where there are different components listed:
+
+- Accordion
+- Buttons
+- Cards/ProductCard
+- Carousel
+- Details
+- **Layout**
+
+Within the layout section, there are different components to manage the page layout, such as AspectRatio, Box, Grid, Stack, and more.
+
+We can also find information about the spaces they use:
+
+```js
+export var getSpacing = function (t) {
+ var n = 'var(--podium-cds-size-spacing-'.concat(t.toLowerCase(), ')');
+ return ['xs', 's', 'm', 'l', 'xl', 'xxl', 'xxxl', 'xxxxl'].includes(t)
+ ? n
+ : t;
+};
+```
+
+Where the values of the variables are:
+
+```css
+--podium-cds-size-spacing-xs: 4px;
+--podium-cds-size-spacing-s: 8px;
+--podium-cds-size-spacing-m: 12px;
+--podium-cds-size-spacing-l: 24px;
+--podium-cds-size-spacing-xl: 36px;
+--podium-cds-size-spacing-xxl: 60px;
+--podium-cds-size-spacing-xxxl: 84px;
+--podium-cds-size-spacing-xxxxl: 120px;
+```
+
+- Loaders/Skeleton
+- NikeDesignSystemProvider
+- PullQuote
+- SelectionControls/Switch
+- SizeChart
+- **Typography/Text**
+
+Here, we can find a mapping that determines which HTML element will be rendered depending on the type of text, aiming for SEO considerations:
+
+```js
+export var componentMapping = {
+ body1: 'p',
+ body2: 'p',
+ body3: 'p',
+ body1Strong: 'p',
+ body2Strong: 'p',
+ body3Strong: 'p',
+ legal: 'p',
+ editorialBody1: 'p',
+ editorialBody1Strong: 'p',
+ oversize1: 'p',
+ oversize2: 'p',
+ oversize3: 'p',
+ display1: 'h1',
+ display2: 'h2',
+ display3: 'h3',
+ display4: 'h4',
+ title1: 'h1',
+ title2: 'h2',
+ title3: 'h3',
+ title4: 'h4',
+ conversation1: 'p',
+ conversation2: 'p',
+ conversation3: 'p',
+ conversation4: 'p',
+};
+```
+
+We can notice that in some components, they make use of tokens imported from @nike/design-system-base, as we can see in the file components/dialog/styles.js:
+
+```js
+import styled from '@emotion/styled';
+import { Modal } from '@nike/nr-sole-modal';
+import tokens from '@nike/design-system-base';
+
+export const StyledModal = styled(Modal)`
+ @media only screen and (max-width: ${tokens.bp('tablet')}px) {
+ transition: visibility 0s ease, bottom 0.5s ease;
+ }
+ & .modal-container {
+ border-radius: 16px;
+ padding: 24px;
+ text-align: left;
+ transform: translateY(50px);
+ transition: opacity 0.6s ease 0.2s, transform 0.4s ease 0.2s,
+ height 0.4s ease;
+ padding: 12px;
+
+ /* desktop media query minus one for ipad pro */
+ @media only screen and (max-width: ${tokens.bp('desktop') - 1}px) {
+ &.modal-container {
+ max-width: 100%;
+ position: fixed;
+ padding: 16px 0 0;
+ bottom: 0px;
+ border-radius: 0;
+ }
+ }
+ }
+`;
+```
+
+From here, we can extract the tokens:
+
+```json
+{
+ "default": {
+ "opts": {
+ "fontSizeUnit": "px"
+ },
+ "ds": {
+ "type": {
+ "baseFontSize": "16px",
+ "sizes": {
+ "xs": "14px",
+ "sm": "20px",
+ "baseline": "16px",
+ "md": "22px",
+ "lg": "26px",
+ "xl": "30px"
+ },
+ "size": {
+ "brandMarketing": {
+ "xs": "14px",
+ "sm": "20px",
+ "baseline": "16px",
+ "md": "60px",
+ "lg": "80px",
+ "xl": "120px"
+ },
+ "desktop": {
+ "xs": "14px",
+ "sm": "20px",
+ "baseline": "16px",
+ "md": "24px",
+ "lg": "28px",
+ "xl": "36px"
+ }
+ },
+ "fontFamily": {
+ "base": "\"Helvetica Neue\", Helvetica, Arial, sans-serif",
+ "brand": "\"Nike TG\", \"Helvetica Neue\", Helvetica, Arial, sans-serif",
+ "marketing": "\"Nike Futura\", \"Helvetica Neue\", Helvetica, Arial, sans-serif"
+ },
+ "lineHeight": {
+ "mobile": {
+ "14": 1.7142857142857142,
+ "16": 1.75,
+ "20": 1.5,
+ "22": 1.4545454545454546,
+ "24": 1.5,
+ "26": 1.3076923076923077,
+ "28": 1.4285714285714286,
+ "30": 1.3333333333333333,
+ "36": 1.3333333333333333,
+ "40": 0.9,
+ "60": 0.8333333333333334,
+ "80": 0.875,
+ "120": 0.8333333333333334
+ },
+ "tablet": {
+ "20": 1.6,
+ "60": 0.9333333333333333
+ }
+ },
+ "fontWeight": {
+ "regular": 400,
+ "medium": 500
+ }
+ },
+ "colors": {
+ "colorPalette": {
+ "black": {
+ "base": "#111111",
+ "light": "rgba(0, 0, 0, 0.75)",
+ "dark": "#0D0D0D"
+ },
+ "white": {
+ "base": "#ffffff",
+ "light": "rgba(255, 255, 255, 0.75)",
+ "dark": "#BFBFBF"
+ },
+ "grey": {
+ "base": "#757575",
+ "light": "rgba(117, 117, 117, 0.75)",
+ "dark": "#585858"
+ },
+ "orange": {
+ "base": "#FA5400",
+ "light": "rgba(250, 84, 0, 0.75)",
+ "dark": "#BC3F00"
+ },
+ "red": {
+ "base": "#d43f21"
+ },
+ "green": {
+ "base": "#128a09"
+ }
+ },
+ "brand": {
+ "black": "#111111",
+ "white": "#ffffff",
+ "grey": "#757575",
+ "orange": "#FA5400",
+ "red": "#d43f21",
+ "green": "#128a09",
+ "inactiveGrey": "#CCCCCC",
+ "borderGrey": "#E5E5E5",
+ "primaryGrey": "#F5F5F5",
+ "secondaryGrey": "#FAFAFA",
+ "accent": "#FA5400",
+ "error": "#d43f21",
+ "success": "#128a09"
+ }
+ },
+ "breakpoints": {
+ "mobile": 0,
+ "tablet": 600,
+ "desktop": 960,
+ "largeDesktop": 1440,
+ "extraLargeDesktop": 1920
+ },
+ "mediaQueries": {
+ "mobile": "only screen and (min-width: 0px)",
+ "sm": "(max-width: 599px)",
+ "md": "(min-width: 600px) and (max-width: 959px)",
+ "lg": "(min-width: 960px) and (max-width: 1439px)",
+ "xl": "(min-width: 1440px) and (max-width: 1919px)",
+ "xxl": "(min-width: 1920px)",
+ "tablet": "only screen and (min-width: 600px)",
+ "desktop": "only screen and (min-width: 960px)",
+ "largeDesktop": "only screen and (min-width: 1440px)",
+ "extraLargeDesktop": "only screen and (min-width: 1920px)"
+ },
+ "zIndex": {
+ "z0": 0,
+ "z1": 1,
+ "z2": 2,
+ "z3": 3,
+ "z4": 4,
+ "z5": 5,
+ "z6": 6,
+ "z7": 7,
+ "z8": 8,
+ "z9": 9,
+ "z10": 10,
+ "low": 0,
+ "mid": 5,
+ "high": 10
+ },
+ "scaleIncrement": 4,
+ "spacing": {
+ "baseline": 16,
+ "padding": "4px",
+ "scale": [
+ 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68,
+ 72, 76, 80, 84, 88, 92, 96, 100, 104, 108, 112, 116
+ ]
+ },
+ "layout": {
+ "gutter": 16,
+ "maxWidth": 1808,
+ "grid": {
+ "columnCount": 12,
+ "columnWidth": {
+ "1": "8.333333333333334%",
+ "2": "16.666666666666668%",
+ "3": "25%",
+ "4": "33.333333333333336%",
+ "5": "41.66666666666667%",
+ "6": "50%",
+ "7": "58.333333333333336%",
+ "8": "66.66666666666667%",
+ "9": "75%",
+ "10": "83.33333333333334%",
+ "11": "91.66666666666667%",
+ "12": "100%"
+ }
+ }
+ },
+ "transition": {
+ "default": {
+ "duration": "250ms",
+ "timing": "cubic-bezier(0.77, 0, 0.175, 1)",
+ "transition": "all 250ms cubic-bezier(0.77, 0, 0.175, 1)"
+ }
+ },
+ "borderRadius": 24
+ }
+ }
+}
+```
+
+Colors:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### Internationalization
+
+If we examine the network requests, we can already identify some requests that include "i18n" in their path, such as on the favorites page (https://www.nike.com/favorites):
+
+***https://www.nike.com/assets/i18n/dotcom-fragments-recommendations/en-US.json***
+
+Here, the response is a JSON object with a key containing an object with "value" and "description" keys:
+
+```json
+{
+ "favorites.productRecommendationsCarousel.title": {
+ "value": "You Might Also Like",
+ "description": "The title for the product recommendations carousel displayed on the favorites page"
+ },
+ "profile.favoritesCarousel.title": {
+ "value": "Favorites",
+ "description": "Title for the favorites section"
+ },
+ "profile.favoritesCarousel.viewAllLink": {
+ "value": "View All",
+ "description": "The link that takes users to the page where they can view all of their Favorites"
+ },
+ "recommendations.badges.member_access_label": {
+ "value": "Member Access",
+ "description": "This text is displayed in carousel card for member exclusive product"
+ },
+ "recommendations.badges.member_exclusive": {
+ "value": "Get this product with your free Nike Membership Profile",
+ "description": "This text is displayed in carousel card for member exclusive product"
+ },
+ "recommendations.badges.nike_by_you": {
+ "value": "Nike by You",
+ "description": "This text is displayed in carousel card for Nike by You product"
+ },
+ "recommendations.badges.nike_by_you_label": {
+ "value": "Customize",
+ "description": "This text is displayed in carousel card for Nike by You product"
+ },
+ "recommendations.favorites.title": {
+ "value": "Find Your Next Favorite",
+ "description": "Default Title of Recommendations"
+ },
+ "recommendations.next.label": {
+ "value": "Next Recommended Product",
+ "description": "Next Button Title"
+ },
+ "recommendations.nullsearch.title": {
+ "value": "These popular items might interest you",
+ "description": "Product wall null search title for recommendations carousel"
+ },
+ "recommendations.previous.label": {
+ "value": "Previous Recommended Product",
+ "description": "Previous Product Label for Button"
+ },
+ "recommendations.top_trending": {
+ "value": "Top Trending",
+ "description": "Title for a component which will display our top trending products for user's market place"
+ }
+}
+```
+
+We can also find translations within the script using the `