<?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: Gun</title>
    <description>The latest articles on DEV Community by Gun (@gunkev).</description>
    <link>https://dev.to/gunkev</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F663140%2Fa75157c3-9645-440a-a0aa-e5d813c606b1.jpeg</url>
      <title>DEV Community: Gun</title>
      <link>https://dev.to/gunkev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gunkev"/>
    <language>en</language>
    <item>
      <title>Best tools to connect AI agents to Slack and Microsoft Teams Today</title>
      <dc:creator>Gun</dc:creator>
      <pubDate>Tue, 06 Oct 2026 10:14:39 +0000</pubDate>
      <link>https://dev.to/gunkev/best-tools-to-connect-ai-agents-to-slack-and-microsoft-teams-today-c2j</link>
      <guid>https://dev.to/gunkev/best-tools-to-connect-ai-agents-to-slack-and-microsoft-teams-today-c2j</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;TL;DR&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Connecting an AI agent to Slack or Microsoft Teams can involve tool calls, incoming events, continuous data sync, or all three.These integrations are also part of the broader &lt;a href="https://www.learn-dev-tools.blog/a-beginners-guide-to-software-development-tools/" rel="noopener noreferrer"&gt;developer toolchain&lt;/a&gt; teams use to build, test, and collaborate on software.&lt;/p&gt;

&lt;p&gt;The best option depends on which of those your product needs and how much integration infrastructure you want to operate yourself.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slack official MCP server&lt;/strong&gt;:  is best for first-party Slack tool access when you can handle events and data ingestion separately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft Work IQ MCP:&lt;/strong&gt; is best for agents built around Teams and the broader Microsoft 365 ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nango:&lt;/strong&gt; is best for customer-facing products that need multi-tenant authentication, actions, incoming events, and continuous Slack or Teams data sync.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composio&lt;/strong&gt;: is best for broad ready-made Slack and Teams actions, especially when Slack triggers are enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arcade:&lt;/strong&gt; is best when authorized and controlled agent tool execution is the main requirement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, Slack and Microsoft now provide first-party MCP options for agents, while platforms such as Nango, Composio, and Arcade provide additional integration infrastructure for the same customer-facing job. That layer handles the parts MCP may not cover by itself&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP tools, triggers, and data sync solve different problems
&lt;/h2&gt;

&lt;p&gt;Connecting an agent to Slack or Microsoft Teams can involve three distinct data flows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent → Slack or Teams:&lt;/strong&gt; The agent searches, reads, posts, replies, or performs another action.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack or Teams → agent:&lt;/strong&gt; Provider activity, such as a new message, starts an agent workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack or Teams → application data:&lt;/strong&gt; Messages and other records stay synchronized for search, RAG, or downstream processing
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fybutij4losae4vso0pn2.gif" alt="mcp model workflow" width="720" height="514"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MCP primarily standardizes how agents discover and invoke tools. Incoming events still need an event-delivery mechanism,k eeping an external dataset current requires an ingestion or synchronization layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What to evaluate when connecting an AI agent to Slack or Teams&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s say you’re building a Slack AI agent or Microsoft Teams agent into your product. These are the main capabilities to evaluate.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent actions&lt;/strong&gt;
Check whether the available tools cover the operations your agent needs, such as searching conversations, reading messages, replying, posting messages, or working with users, chats, and channels. Tool count helps, but it does not tell you whether the tools map to the workflow your agent needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incoming events and triggers&lt;/strong&gt;
If provider activity should &lt;a href="https://nango.dev/blog/how-to-make-ai-agents-react-to-api-webhooks" rel="noopener noreferrer"&gt;start an agent workflow&lt;/a&gt;, check how events reach your application.
With provider-native integrations, you work with Slack's Events API or Microsoft Graph change notifications. An integration platform can add delivery, tenant routing, retries, and other event infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous data sync&lt;/strong&gt;
On-demand retrieval is different from maintaining your own current dataset. For search or RAG, check whether the product can backfill existing data and keep it updated incrementally, or whether your team has to build that ingestion pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication and multi-tenancy&lt;/strong&gt;
For customer-facing integrations, each customer must authorize their own connection. Check how the solution handles OAuth, credentials, customer connections, tenant isolation, and mapping incoming activity to the correct connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production controls&lt;/strong&gt;
Production requirements go beyond a successful tool call. Look at observability, permissions, failure handling, customization, and deployment responsibilities. These determine how much infrastructure your team still owns.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 5 best tools to connect AI agents to Slack and Microsoft Teams
&lt;/h2&gt;

&lt;h2&gt;
  
  
  1. Slack official MCP server
&lt;/h2&gt;

&lt;p&gt;Slack's hosted MCP server gives agents first-party access to Slack through MCP. It supports operations such as searching workspace content, reading channels and threads, sending messages, creating conversations, adding reactions, and working with users and canvases.&lt;br&gt;
The Slack MCP server exposes these capabilities as tools that compatible MCP clients can discover and invoke.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffemz8hc512qkf791h6to.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffemz8hc512qkf791h6to.png" alt="slack official mcp server" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; &lt;br&gt;
First-party Slack tool access when you can handle events and data ingestion separately.&lt;br&gt;
&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First-party Slack integration:&lt;/strong&gt; Slack operates the MCP server and ties access to registered Slack apps, OAuth scopes, rate limits, app approval, and audit controls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong read and write coverage:&lt;/strong&gt; Agents can search Slack, retrieve conversation history, send messages, create conversations, add reactions, and access other Slack resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No additional integration platform required for MCP tool access:&lt;/strong&gt; A compatible MCP client can connect directly to Slack's hosted server and invoke Slack tools. You still need a separate agent or model layer if the application needs to interpret natural-language requests and decide which tools to call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Incoming events use separate Slack infrastructure:&lt;/strong&gt; The MCP server handles agent-callable tools. Message-driven workflows still rely on Slack's Events API, delivered through HTTP or Socket Mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No managed external sync layer:&lt;/strong&gt; The MCP server can retrieve Slack data, but it does not provide a managed backfill and incremental-sync layer for keeping your own datastore or RAG index current.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack-specific:&lt;/strong&gt; Supporting Microsoft Teams or other APIs means operating those integrations separately or adding another integration layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP app distribution is restricted:&lt;/strong&gt; Slack currently allows MCP access only for &lt;a href="https://docs.slack.dev/ai/slack-mcp-server/" rel="noopener noreferrer"&gt;internal apps and apps published in the Slack Marketplace&lt;/a&gt; which means unlisted apps cannot use the Slack MCP server.
## 2. Microsoft Work IQ MCP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microsoft’s Work IQ MCP surface is split across two patterns. Work IQ Teams gives agents Teams-specific tools for chats, channels, users, members, and messages. The broader Work IQ MCP interface uses 10 generic tools over Microsoft 365 resource paths, so an agent can work with Teams data alongside mail, calendar, files, and other Microsoft 365 context.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd76id0i5v3689mtcnk7t.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd76id0i5v3689mtcnk7t.png" alt="microsoft work iq mcp" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; &lt;br&gt;
Microsoft 365-native agents that need Teams access as part of a broader Microsoft work-data environment.&lt;br&gt;
&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First-party Teams and Microsoft 365 access:&lt;/strong&gt; The dedicated Teams MCP server exposes chat, channel, message, team, and member operations directly through Microsoft’s own agent infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broader Microsoft context:&lt;/strong&gt; Work IQ is designed around Microsoft 365 data and context rather than treating Teams as an isolated integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong governance:&lt;/strong&gt; Agent 365 provides admin controls, scoped access, policy enforcement, and tool-call observability through the Microsoft 365 control plane.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Incoming Teams events use separate infrastructure:&lt;/strong&gt; MCP handles agent tool access. Message-driven workflows still use Microsoft Graph change notifications, which have their own subscription and lifecycle requirements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No managed external sync layer:&lt;/strong&gt; Work IQ MCP provides retrieval and actions, but not a managed backfill and incremental-sync layer for keeping your own Teams datastore or RAG index current.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/microsoft-agent-365/mcp-server-reference/admintools" rel="noopener noreferrer"&gt;&lt;strong&gt;Work IQ MCP is still in preview&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;:&lt;/strong&gt; So functionality and availability can change before general availability..&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft 365 Copilot required:&lt;/strong&gt; Users need a Microsoft 365 Copilot license to access the Work IQ MCP servers through Agent 365.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft-specific:&lt;/strong&gt; Products that also need Slack or other APIs still need separate integration infrastructure.
## 3. Nango&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://nango.dev/" rel="noopener noreferrer"&gt;Nango&lt;/a&gt; lets you build Slack and Microsoft Teams integrations for AI agents inside customer-facing products. It supports 900+ APIs and 6,000+ reusable templates, with auth, actions, webhooks, and continuous syncs running on the same integration layer. Coding agents can build and customize the functions, while Nango handles customer connections, credentials, and the runtime behind them.&lt;/p&gt;

&lt;p&gt;For Slack, Nango provides pre-built actions and continuous syncs, including &lt;code&gt;[messages-received](https://nango.dev/docs/api-integrations/slack)&lt;/code&gt; for messages, thread replies, and reactions. For Microsoft Teams, Nango’s Teams &lt;a href="https://nango.dev/docs/api-integrations/microsoft-teams#-pre-built-syncs-&amp;amp;-actions-for-microsoft-teams" rel="noopener noreferrer"&gt;syncs&lt;/a&gt; cover channel messages, channel replies, chat messages, chats, users, teams, members, and channels. The same integration layer can cover the CRM, support, calendar, storage, and other APIs the agent needs next.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; &lt;br&gt;
Customer-facing products that need multi-tenant Slack and Teams integrations with actions, provider webhooks, and continuously synchronized data.&lt;br&gt;
&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actions and syncs are separate primitives:&lt;/strong&gt; Actions handle on-demand API operations, while syncs maintain records over time with checkpoints and incremental change delivery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built for customer connections:&lt;/strong&gt; Functions run with the credentials and scopes of a specific connection, which makes the same integration logic reusable across customer accounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack events can be routed to the correct customer connection:&lt;/strong&gt; Nango's &lt;a href="https://nango.dev/docs/api-integrations/slack/webhooks" rel="noopener noreferrer"&gt;Slack webhook flow&lt;/a&gt; maps Events API payloads using team_id before forwarding them to the application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous Slack and Teams sync:&lt;/strong&gt; Slack's messages-received sync and Teams' messages sync provide incremental data ingestion rather than only on-demand retrieval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retries and observability are built in:&lt;/strong&gt; Functions include retries and rate-limit handling, with logs, records storage, checkpoints, and OpenTelemetry export for monitoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Webhook setup still depends on the provider:&lt;/strong&gt; Nango handles routing and connection matching, but Slack events still have to be subscribed to in the Slack app, and other providers may require their own registration flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud usage has platform limits:&lt;/strong&gt; Nango Cloud applies API rate limits, execution limits, webhook retry limits, and retention rules. For example, action functions have a 15-minute execution limit and outgoing webhooks are retried twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Costs grow with integration usage:&lt;/strong&gt; Functions, sync storage, and processed webhooks are usage-based once the included plan allowances are exceeded.
## 4. Composio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Composio connects AI agents to third-party apps through toolkits that bundle authentication, tools, and triggers. Each customer connects their own account, and the agent can call tools without handling provider credentials directly.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkb1qxpppubhpka10aux1.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkb1qxpppubhpka10aux1.png" alt="composio" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; &lt;br&gt;
Agent products that prioritize broad ready-made Slack and Teams actions, especially when managed real-time Slack triggers are also required.&lt;br&gt;
&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Broad Slack and Teams action coverage:&lt;/strong&gt; 167 Slack tools and 169 Microsoft Teams tools cover messaging, conversations, users, channels, meetings, files, and other provider operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed real-time Slack triggers:&lt;/strong&gt; Nine Slack triggers can start agent workflows from events in connected workspaces, with delivery, retries, and webhook signing handled by Composio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Triggers stay tied to the customer connection:&lt;/strong&gt; Each trigger belongs to a specific user and connected account, so incoming events retain the customer context the agent needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed authentication for Slack and Teams:&lt;/strong&gt; Composio handles OAuth, token storage, and refreshes, while custom auth lets you use your own app credentials and scopes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slack has stronger trigger coverage:&lt;/strong&gt; The Slack toolkit exposes nine trigger types, while the Microsoft Teams toolkit currently has tools but no trigger section.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous sync is not part of the Slack or Teams toolkit model:&lt;/strong&gt; The current integrations focus on tools and triggers rather than a managed backfill and incremental-sync layer for maintaining your own datastore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Slack and Teams ingestion still depends on trigger support:&lt;/strong&gt; Composio triggers are tied to the trigger types each toolkit exposes. If your workflow needs message-triggered ingestion, backfill, or custom reconciliation, you may need to build that logic outside Composio.
## 5. Arcade&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Arcade gives AI agents tools that run with user authorization. Its Slack and Microsoft Teams integrations let agents act on behalf of individual users, while Arcade handles the OAuth flow, token storage, refreshes, and authorization checks around tool execution.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F71ndrd9ifuyagdgyuw93.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F71ndrd9ifuyagdgyuw93.png" alt="Arcade" width="799" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; &lt;br&gt;
Agent products where user-authorized tool execution is the main requirement and incoming events or continuous data sync are not.&lt;br&gt;
&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authorization is part of tool execution:&lt;/strong&gt; Arcade checks whether the user has granted the scopes a tool needs and starts an OAuth flow when additional authorization is required. Tokens are injected into the tool context without being exposed to the model or client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent-focused Slack tools:&lt;/strong&gt; the 10-tool Slack toolkit covers conversation discovery, message and thread retrieval, sending messages, user lookup, and channel membership workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower-level Slack coverage when you need it:&lt;/strong&gt; a separate 73-tool Slack API toolkit exposes a much broader set of Slack operations, including administration, calls, files, search, scheduling, profiles, and workspace management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Useful Teams action coverage:&lt;/strong&gt; 25 Microsoft Teams tools cover chat and channel messaging, discovery, teams and members, user search, and cross-scope message search.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No provider-event trigger layer for Slack or Teams:&lt;/strong&gt; The Slack and Teams integrations expose authenticated tools for agent calls. If Slack or Teams activity needs to start a workflow, you still need a separate event-delivery path such as Slack Events API, Socket Mode, or Microsoft Graph notifications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No managed Slack or Teams data sync:&lt;/strong&gt; The toolkits provide on-demand read and write operations, but not a backfill and incremental-sync layer for keeping your own RAG index or datastore synchronized.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimized Slack and Teams coverage is narrower:&lt;/strong&gt; Arcade’s optimized Slack toolkit lists 10 tools and its Microsoft Teams toolkit lists 25 tools. Less common Slack or Teams workflows may require lower-level tooling or custom tools.
## Comparison table&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Criterion&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Slack official MCP&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Microsoft Work IQ MCP&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Nango&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Composio&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Arcade&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Product model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;First-party Slack MCP server&lt;/td&gt;
&lt;td&gt;First-party Work IQ MCP for Microsoft 365 intelligence&lt;/td&gt;
&lt;td&gt;Customer-facing AI agent / SaaS API integrations&lt;/td&gt;
&lt;td&gt;Agent integration and tool platform&lt;/td&gt;
&lt;td&gt;MCP runtime for agent auth, tool execution, and governance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Slack + Teams coverage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slack only&lt;/td&gt;
&lt;td&gt;Teams + broader Microsoft 365 / Work IQ context&lt;/td&gt;
&lt;td&gt;Slack + Teams&lt;/td&gt;
&lt;td&gt;Slack + Teams&lt;/td&gt;
&lt;td&gt;Slack + Teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent actions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;First-party Slack MCP tools for search, messages, canvases, users, reactions, and channels&lt;/td&gt;
&lt;td&gt;10 generic tools over Microsoft 365 resource paths&lt;/td&gt;
&lt;td&gt;75 pre-built + custom Slack/Teams actions&lt;/td&gt;
&lt;td&gt;Broad pre-built Slack + Teams toolkits&lt;/td&gt;
&lt;td&gt;Optimized Slack + Teams tools, plus custom tools/MCP servers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Incoming events&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Separate Slack Events API or Socket Mode setup&lt;/td&gt;
&lt;td&gt;Separate Microsoft Graph change notifications setup&lt;/td&gt;
&lt;td&gt;Webhook infrastructure with per-customer connection routing&lt;/td&gt;
&lt;td&gt;Slack trigger infrastructure documented; Teams trigger coverage less clear&lt;/td&gt;
&lt;td&gt;Separate provider event infrastructure or custom event handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context &amp;amp; sync&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;On-demand Slack retrieval through MCP tools&lt;/td&gt;
&lt;td&gt;On-demand Microsoft 365 context and data access&lt;/td&gt;
&lt;td&gt;Managed incremental Slack + Teams syncs&lt;/td&gt;
&lt;td&gt;No managed Slack/Teams sync layer; context retrieval happens through tools&lt;/td&gt;
&lt;td&gt;No managed Slack/Teams sync layer; context retrieval happens through tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Customer connection model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slack app + confidential OAuth + workspace/admin approval&lt;/td&gt;
&lt;td&gt;Microsoft Entra ID + Work IQ permissions/policy&lt;/td&gt;
&lt;td&gt;Per-customer connections + managed credentials&lt;/td&gt;
&lt;td&gt;Connected accounts + managed or custom auth&lt;/td&gt;
&lt;td&gt;Per-user authorization at tool execution time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Customization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slack-defined MCP tool surface&lt;/td&gt;
&lt;td&gt;Microsoft-defined generic tools + resource paths&lt;/td&gt;
&lt;td&gt;Custom actions, syncs, webhook functions, and MCP/tool exposure with the &lt;a href="https://nango.dev/docs/getting-started/coding-agent-setup" rel="noopener noreferrer"&gt;Function Builder skill&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Custom tools/toolkits plus managed auth&lt;/td&gt;
&lt;td&gt;Custom tools, custom MCP servers, MCP gateways&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Observability / controls&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slack app controls, OAuth scopes, rate limits, MCP audit logs&lt;/td&gt;
&lt;td&gt;Policy enforcement, scoped access, tool-call tracing, admin governance&lt;/td&gt;
&lt;td&gt;Logs, retries, checkpoints, rate-limit handling, OpenTelemetry&lt;/td&gt;
&lt;td&gt;Execution logs, session state, trigger management&lt;/td&gt;
&lt;td&gt;Policy hooks, authorization controls, gateway controls, OpenTelemetry/audit logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Other API coverage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slack only&lt;/td&gt;
&lt;td&gt;Microsoft 365 / Work IQ context&lt;/td&gt;
&lt;td&gt;900+ APIs across communication, CRM, support, calendar, storage&lt;/td&gt;
&lt;td&gt;Broad app/toolkit catalog for agent tools&lt;/td&gt;
&lt;td&gt;Broad MCP/tool catalog for agent actions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to read the comparison
&lt;/h2&gt;

&lt;p&gt;The table separates tool access, incoming events, and continuous sync because they are different parts of the integration.&lt;br&gt;
A product can expose strong Slack or Teams tools without handling message-triggered workflows or keeping a current copy of the data.&lt;br&gt;
When current technical docs do not clearly show a capability, the table leaves it as a gap instead of inferring it from nearby features.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the best option if my product needs both Slack and Microsoft Teams?
&lt;/h3&gt;

&lt;p&gt;Nango is the best fit if your product needs both providers and you want customer connections, actions, provider webhooks, and continuous sync behind the same integration layer. It manages customer connections and credentials, while letting you build actions, process provider webhooks, and maintain incremental syncs for Slack and Teams.&lt;br&gt;
If your requirements are narrower, another option may fit better. Slack's official MCP server and Microsoft Work IQ provide first-party access within their respective ecosystems. Composio emphasizes broad pre-built agent tools and managed triggers, while Arcade focuses on authorized tool execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need MCP to connect an AI agent to Slack or Microsoft Teams?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;No.&lt;/strong&gt; You &lt;a href="https://www.learn-dev-tools.blog/best-postman-alternatives-developers/" rel="noopener noreferrer"&gt;can call the APIs&lt;/a&gt; or Microsoft Graph directly from an agent application. MCP provides a standard interface for exposing those capabilities as tools, but it is not required to build the integration. It also solves only part of the problem. Customer OAuth, incoming events, credential management, and continuous data sync may still need separate infrastructure depending on the product.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I trigger an AI agent from a Slack or Teams message?
&lt;/h3&gt;

&lt;p&gt;Use the provider's event infrastructure. For Slack, subscribe to message events through the Events API and receive them over HTTP or Socket Mode. For Microsoft Teams, Microsoft Graph change notifications can notify your application about changes to subscribed chat or channel messages.&lt;br&gt;
Your application can then identify the customer connection, start the agent workflow, and give the agent the appropriate Slack or Teams tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should my agent retrieve Slack/Teams context live or sync it for RAG?
&lt;/h3&gt;

&lt;p&gt;Live retrieval works well when an agent needs a small amount of current context for a specific task. You query Slack or Microsoft 365 when the agent needs the information and avoid maintaining another copy of the data. Continuous sync fits products that maintain their own search index, vector store, or historical corpus for RAG. You can backfill existing conversations and ingest changes incrementally instead of rebuilding the dataset whenever the agent needs context.&lt;/p&gt;

&lt;h3&gt;
  
  
  How should customer authentication work for Slack and Teams agents?
&lt;/h3&gt;

&lt;p&gt;Each customer's Slack workspace or Microsoft tenant should have its own authenticated connection and permissions. The agent then performs actions using the credentials associated with that customer rather than a shared user credential.&lt;br&gt;
Slack typically uses an OAuth app installation with the required scopes. Direct Microsoft Teams integrations typically use Microsoft Entra authorization and Microsoft Graph permissions. For a multi-tenant product, an integration platform can &lt;a href="https://nango.dev/blog/guide-to-secure-ai-agent-api-authentication/" rel="noopener noreferrer"&gt;manage these connections and credential lifecycles&lt;/a&gt; while your application maps them to its own users or organizations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use the official Slack and Microsoft MCP servers in a customer-facing product?
&lt;/h3&gt;

&lt;p&gt;Potentially, but their deployment constraints can affect the architecture.&lt;br&gt;
Slack's hosted MCP server is currently limited to internal apps and apps published in the Slack Marketplace.&lt;br&gt;
Microsoft's Work IQ MCP experience in Copilot Studio is currently in preview and requires a Microsoft 365 Copilot license. Microsoft also documents a broader Work IQ API model with usage-based billing, so the right path depends on how your product is built.&lt;br&gt;
If those constraints do not fit, you can build against the underlying Slack and Microsoft APIs or use an integration platform that manages the customer-facing integration layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The right Slack or Microsoft Teams integration depends on what your agent needs beyond tool calls. Provider-native MCP servers work well when you mainly need first-party access inside one ecosystem. Customer-facing products also need per-customer authentication, provider webhooks, and sometimes continuously synced data for search or RAG.&lt;br&gt;
Nango is strongest when those requirements need to work across both Slack and Microsoft Teams. It brings customer connections, actions, provider webhooks, and incremental syncs into one integration layer, while still letting you customize the logic in code.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building an SEO competitor-analysis Actor with Crawlee and Apify</title>
      <dc:creator>Gun</dc:creator>
      <pubDate>Wed, 24 Jun 2026 10:03:31 +0000</pubDate>
      <link>https://dev.to/gunkev/building-an-seo-competitor-analysis-actor-with-crawlee-and-apify-31ke</link>
      <guid>https://dev.to/gunkev/building-an-seo-competitor-analysis-actor-with-crawlee-and-apify-31ke</guid>
      <description>&lt;p&gt;I manage a client's small blog where I regularly publish comparison articles, the kind that rank for queries like "best project management tools" or "windows OS alternatives." After a while, I noticed a pattern: competitors ranking above me weren't necessarily covering more topics, they were just structuring their content differently. I wanted to understand exactly what they were covering that I wasn't.&lt;/p&gt;

&lt;p&gt;My first instinct was to manually open each page, scan the headings, and take notes. That worked for one or two pages. It didn't scale. So I built a Crawlee Actor that does the comparison for me, extracting heading structures from my page and competitor pages, cleaning and normalizing them, and producing a structured JSON output that shows shared topics, missing coverage, and unique sections.&lt;/p&gt;

&lt;p&gt;This article walks through how I built it, what broke along the way, and how I ended up with something reliable enough to run on a monthly schedule.&lt;/p&gt;

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

&lt;p&gt;To follow along, you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js v18 or higher&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://docs.apify.com/cli/" rel="noopener noreferrer"&gt;Apify CLI&lt;/a&gt; installed (&lt;code&gt;npm install -g apify-cli&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Basic familiarity with JavaScript and async/await&lt;/li&gt;
&lt;li&gt;An Apify account (free tier works fine)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the Actor does
&lt;/h2&gt;

&lt;p&gt;By the end of this guide, I'll have a Crawlee Actor that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crawls my page and competitor pages targeting a given topic&lt;/li&gt;
&lt;li&gt;Extracts structured headings (H2, H3, H4) from real article content&lt;/li&gt;
&lt;li&gt;Removes noise from widgets, sidebars, and injected elements&lt;/li&gt;
&lt;li&gt;Normalizes headings into comparable values&lt;/li&gt;
&lt;li&gt;Extracts meaningful entities from headings&lt;/li&gt;
&lt;li&gt;Compares my page against competitors&lt;/li&gt;
&lt;li&gt;Returns a structured JSON output with:

&lt;ul&gt;
&lt;li&gt;shared sections&lt;/li&gt;
&lt;li&gt;missing topics&lt;/li&gt;
&lt;li&gt;unique coverage&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This output gives me a usable baseline for content gap analysis, without opening a single competitor page manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this problem is harder than it looks
&lt;/h2&gt;

&lt;p&gt;At first, comparing pages sounds straightforward. Extract headings from your page, extract headings from competitor pages, and compare them. In practice, this breaks quickly.&lt;/p&gt;

&lt;p&gt;Real-world pages are not clean. They mix actual content with unrelated elements that interfere with extraction. Some of the most common issues I ran into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Noisy sections: Pages often include sidebars, "related articles", newsletters, and promotional blocks. These elements contain headings that are not part of the core content but still get scraped.&lt;/li&gt;
&lt;li&gt;Inconsistent structure: One page may list tools under H2 headings, while another uses H3 or even plain text. The hierarchy is not reliable across sites.&lt;/li&gt;
&lt;li&gt;Injected or styled content: Some sites inject styles or scripts directly into heading elements. Instead of clean text, I was extracting CSS fragments or broken strings like &lt;code&gt;.css-19a5n3-link{color:#0a0a23}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ambiguous headings: Headings like "Best options for teams" or "Top picks" don't clearly identify what they refer to. They are not usable for comparison without further processing.&lt;/li&gt;
&lt;li&gt;Different wording for the same concept: One page may use "Linux Mint", another "Mint Linux". Without normalization, these appear as different items.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you stop at raw extraction, the output becomes noisy and misleading. The real challenge is not scraping — it is isolating the actual article content, removing irrelevant sections, normalizing headings, extracting meaningful entities, and making the data comparable across pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project setup
&lt;/h2&gt;

&lt;p&gt;I started by creating a new Crawlee project and installing the required dependencies. If you're starting from scratch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx crawlee create seo-content-gap-analyzer
&lt;span class="nb"&gt;cd &lt;/span&gt;seo-content-gap-analyzer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you already have a project, install Crawlee:&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;crawlee
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since this workflow is designed to run as an Actor, I also installed the Apify SDK:&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;apify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the main logic lives in a single entry file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/main.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all the setup I needed to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the Actor input
&lt;/h2&gt;

&lt;p&gt;The Actor needs two main inputs: my page (the one I want to analyze) and competitor pages (used for comparison). I also included a topic label and a few optional settings. Here's an example input for &lt;strong&gt;Windows OS alternatives&lt;/strong&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"comparison"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"topic"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"windows os alternatives"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"myPage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-site.com/windows-os-alternatives"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"competitors"&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;"https://example.com/windows-alternatives"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/best-windows-alternatives"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/linux-vs-windows-alternatives"&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;"maxRequests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"debug"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Input breakdown&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;type&lt;/strong&gt;: Defines the output mode. Here, I'm running a comparison workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;topic&lt;/strong&gt;: A label describing what the analysis is about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;myPage&lt;/strong&gt;: The page I want to evaluate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;competitors&lt;/strong&gt;: A list of URLs ranking for the same topic. These serve as the comparison baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;maxRequests&lt;/strong&gt; &lt;em&gt;(optional)&lt;/em&gt;: Limits how many pages the crawler processes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;debug&lt;/strong&gt; &lt;em&gt;(optional)&lt;/em&gt;: Enables additional logging during execution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At runtime, the Actor will crawl my page and each competitor page, extract heading structures from all of them, then compare everything to identify overlaps and gaps. This input structure keeps the workflow flexible and reusable across topics. If you want to add validation or a UI for your inputs, the &lt;a href="https://docs.apify.com/platform/actors/development/actor-definition/input-schema/specification/v1" rel="noopener noreferrer"&gt;Actor input schema specification&lt;/a&gt; covers how to set that up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crawling strategy
&lt;/h2&gt;

&lt;p&gt;For this workflow, I used &lt;a href="https://crawlee.dev/api/cheerio-crawler/class/CheerioCrawler" rel="noopener noreferrer"&gt;CheerioCrawler&lt;/a&gt;. This was a deliberate choice. I'm not interacting with pages, clicking buttons, or handling dynamic user flows. The goal is to extract structured content from article pages as efficiently as possible. CheerioCrawler gives me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fast HTML parsing&lt;/li&gt;
&lt;li&gt;low resource usage&lt;/li&gt;
&lt;li&gt;simple DOM traversal&lt;/li&gt;
&lt;li&gt;enough control to clean and process content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using a browser-based crawler like Playwright would increase complexity and cost without adding real value for this use case.&lt;/p&gt;

&lt;p&gt;Working with real-world comparison pages quickly exposed edge cases that don't appear in simple demos. The main challenge was not crawling itself, but handling inconsistent and deeply nested HTML structures. Some pages exposed tools cleanly, while others embedded them inside component-based layouts, mixed with styling layers and editorial content.&lt;/p&gt;

&lt;p&gt;To make the crawler reliable, I focused on three areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text normalization: to remove CSS fragments and presentation artifacts before processing&lt;/li&gt;
&lt;li&gt;Entity filtering: to separate actual tools from scores, features, and editorial sections&lt;/li&gt;
&lt;li&gt;Flexible matching: to handle variations in naming across pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These adjustments didn't change how the crawler works at a high level, but they significantly improved the quality of the extracted data.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I actually need from each page
&lt;/h3&gt;

&lt;p&gt;I'm not scraping the entire page. I only care about the main article content, heading structure (H2, H3, H4), and meaningful text inside those headings. Everything else is noise.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crawler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CheerioCrawler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;maxRequestsPerCrawl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;requestHandler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;head &amp;gt; title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&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;metaDescription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meta[name="description"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&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;h1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&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;canonical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link[rel="canonical"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;href&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&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;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aside&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.sidebar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.widget&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.related&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.latest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.recommend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.newsletter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;removeWidgetBlocks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&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;articleRoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getArticleRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;h2List&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="nx"&gt;articleRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;el&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;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeHeading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ignoredHeadings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;h2List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalized&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;h3List&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="nx"&gt;articleRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;el&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;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normalizeHeading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ignoredHeadings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;h3List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalized&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;h2List&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;uniqueList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h2List&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;h3List&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;uniqueList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h3List&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pushData&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="na"&gt;pageType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;myPage&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myPage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;competitor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;metaDescription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;h2List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;h2Count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;h2List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;h3List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;h3Count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;h3List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;checkedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crawler&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="nx"&gt;myPage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;competitorUrls&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what the initial extraction looks like for three of the pages I tested:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0cen0qhw1iuyt5iehk47.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0cen0qhw1iuyt5iehk47.png" alt="Initial scrape result from how2shout.com showing raw heading extraction" width="800" height="392"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuuh065ue3mg19flbzo9y.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuuh065ue3mg19flbzo9y.png" alt="Initial scrape result from learn-dev-tools.blog" width="800" height="396"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd5cc7jedkrpmxi37fwuo.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd5cc7jedkrpmxi37fwuo.png" alt="Initial scrape result from PCMag showing noisy heading output" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a comparison snapshot
&lt;/h2&gt;

&lt;p&gt;At this point, each page was returning structured headings — useful, but not yet comparable. To get a global view, I introduced a comparison layer that aggregates all pages into a single snapshot. The goal is not to change extraction, but to summarize what appears across pages, what is missing from my page, and what is unique.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;comparison&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sharedHeadings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;myPageOnlyHeadings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;competitorOnlyHeadings&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added a helper function just below &lt;code&gt;uniqueList()&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;compareHeadings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myPageData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;competitorsData&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;myItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;uniqueList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;myPageData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h3List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;myPageData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h4List&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[])]&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isMeaningfulHeading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;extractLabel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalizeComparisonLabel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;looksLikeEntityLabel&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;compItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;uniqueList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;competitorsData&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h3List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;h4List&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[])])&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isMeaningfulHeading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;extractLabel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalizeComparisonLabel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;looksLikeEntityLabel&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;sharedHeadings&lt;/span&gt; &lt;span class="o"&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;myPageOnlyHeadings&lt;/span&gt; &lt;span class="o"&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;competitorOnlyHeadings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;for &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;myItem&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;myItems&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;hasMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;compItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;compItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;areHeadingsSimilar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;compItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasMatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;sharedHeadings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;myPageOnlyHeadings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myItem&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;for &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;compItem&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;compItems&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;hasMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;myItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;myItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;areHeadingsSimilar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;myItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasMatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;competitorOnlyHeadings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compItem&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;sharedHeadings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uniqueList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sharedHeadings&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;myPageOnlyHeadings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uniqueList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myPageOnlyHeadings&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;competitorOnlyHeadings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uniqueList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;competitorOnlyHeadings&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn't change the crawler itself. It adds a snapshot layer on top of the extracted data — one summary that shows how my page compares to the rest. After the crawl finishes, I load the dataset and split results into &lt;code&gt;myPage&lt;/code&gt; and &lt;code&gt;competitor&lt;/code&gt; entries:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}&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;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&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;myPageData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&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;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myPage&lt;/span&gt;&lt;span class="dl"&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;competitorsData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&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;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;competitor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I run the comparison and store the snapshot:&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myPageData&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;competitorsData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;comparison&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compareHeadings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myPageData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;competitorsData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Comparison Result:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;comparison&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Actor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pushData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;comparison&lt;/span&gt;&lt;span class="dl"&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;comparison&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;generatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives a first global view of the topic: &lt;code&gt;sharedHeadings&lt;/code&gt; shows overlap between my page and competitors, &lt;code&gt;myPageOnlyHeadings&lt;/code&gt; shows content covered only on my page, and &lt;code&gt;competitorOnlyHeadings&lt;/code&gt; shows content competitors cover that my page doesn't.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjt5lggtfomyhr3bjsg48.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjt5lggtfomyhr3bjsg48.png" alt="First comparison summary result showing raw heading overlap before cleanup" width="799" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the crawler on another topic
&lt;/h2&gt;

&lt;p&gt;At this point, the crawler worked for one dataset. The next step was to check whether the logic held when the topic changed. Instead of modifying the code, I only changed the input. This time using real project management pages I already had bookmarked:&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;"topic"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project management tools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"myPage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.learn-dev-tools.blog/best-legal-project-management-software/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"competitors"&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;"https://www.paymoapp.com/blog/project-management-software/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://project-management.com/top-10-project-management-software/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://zapier.com/blog/free-project-management-software"&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;p&gt;Here's what the individual page extractions looked like:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvwg8w9mcpdoi5uu1mhu8.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvwg8w9mcpdoi5uu1mhu8.png" alt="Scrape result from learn-dev-tools.blog for project management topic" width="800" height="389"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwsav46ly5n1gaf2rjsxd.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwsav46ly5n1gaf2rjsxd.png" alt="Scrape result from paymoapp.com" width="799" height="398"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fahu5a34kkylioeatcyrb.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fahu5a34kkylioeatcyrb.png" alt="Scrape result from zapier.com blog" width="799" height="398"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxg2sotxl1hgh22e3j4p1.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxg2sotxl1hgh22e3j4p1.png" alt="Scrape result from project-management.com" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's the comparison summary for this run — this is the output I actually use to plan my content updates:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv65catzmugw8cbjorj73.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv65catzmugw8cbjorj73.png" alt="Comparison summary result for project management topic" width="799" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This one was more telling. The mainstream tools were well covered on my end — monday.com, ClickUp, Trello, Asana, Wrike, Smartsheet, Basecamp all showed up in the shared list. But the &lt;code&gt;competitorOnlyHeadings&lt;/code&gt; was long. Jira, Airtable, Zoho, Teamwork, Podio, Hive — tools I hadn't touched at all. The &lt;code&gt;myPageOnlyHeadings&lt;/code&gt; also caught something I hadn't noticed: noise entries like "but what are" and "key features" were still leaking through, which meant the entity filtering still needed tuning for this type of content. The legal-specific tools I covered — Clio, Thomson Reuters — didn't appear anywhere in the competitor set, which made sense given the article's focus, but it also explained a big chunk of the gap.&lt;/p&gt;

&lt;p&gt;Inspecting the Zapier page structure revealed exactly the kind of noise problem I mentioned earlier:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8r96lq4yocynlp0bnxpv.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8r96lq4yocynlp0bnxpv.png" alt="Zapier HTML structure showing CSS-polluted heading elements" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Extracting tools from comparison pages feels simple, I expected them to sit cleanly in H2 or H3 tags. But once I tried it across a few sites, that assumption broke. Some pages use H3 lists, others use H2 with long descriptions, and more complex ones don't expose tools as clear headings at all.&lt;/p&gt;

&lt;p&gt;One problem was noise. The same heading level mixed tool names, scores, and editorial sections. If I extracted everything blindly, I ended up treating all of it as the same type of data. Another issue was that the DOM isn't just content, on modern pages, styled components wrap everything, so I started pulling CSS fragments or UI text alongside the actual tool names. The &lt;code&gt;h3List&lt;/code&gt; for Zapier pages was polluted with text like &lt;code&gt;.css-19a5n3-link{...}&lt;/code&gt; followed by the actual entity text.&lt;/p&gt;

&lt;p&gt;And even when headings looked clean, their meaning wasn't consistent. A tool might be in H3 on one page and in H2 on another, while lower levels handled features or pricing. So relying on a fixed heading level worked sometimes, but quietly failed on others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhancing the extraction
&lt;/h2&gt;

&lt;p&gt;I needed to refine the results to stop getting polluted text like &lt;code&gt;.css-19a5n3-link{...}&lt;/code&gt;. To fix this:&lt;/p&gt;

&lt;p&gt;First, I added a text-cleaning layer to strip CSS fragments and normalize headings before comparison:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;cleanExtractedText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;css-&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;{&lt;/span&gt;&lt;span class="se"&gt;\s]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(?:\[[^\]]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\])?\{[^&lt;/span&gt;&lt;span class="sr"&gt;}&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\}&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/@media&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;{&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\{[^&lt;/span&gt;&lt;span class="sr"&gt;}&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\}&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-z-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;;{}&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+;/gi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="sr"&gt;00a0/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&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;normalizeHeading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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="nf"&gt;cleanExtractedText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&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;normalizeForComparison&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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="nf"&gt;cleanExtractedText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;com$/i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;–—&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;[\.]\s&lt;/span&gt;&lt;span class="sr"&gt;*/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\([^&lt;/span&gt;&lt;span class="sr"&gt;)&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;\)\s&lt;/span&gt;&lt;span class="sr"&gt;*$/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^\w\s&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\-\.]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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;Second, I tightened entity detection instead of treating every heading as a tool. This removes scores, FAQs, pricing sections, action phrases, and other review scaffolding from the comparison layer:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isEntityLike&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonicalizeEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entityBlocklist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isNumericLike&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isQuestionLike&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;startsWithAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isDetailLike&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&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;isMeaningfulHeading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonicalizeEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isBoilerplateHeading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;Third, I improved matching by canonicalizing entity labels and comparing them more flexibly. This is what merges things like &lt;code&gt;monday work management&lt;/code&gt; into &lt;code&gt;monday.com&lt;/code&gt;, or &lt;code&gt;jira software cloud&lt;/code&gt; into &lt;code&gt;jira&lt;/code&gt;, instead of treating them as separate tools:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;entity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extractEntityFromHeading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;entity&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;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonicalizeEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getComparisonTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;areHeadingsSimilar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;na&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonicalizeEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&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;nb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;canonicalizeEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;na&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;na&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;na&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;na&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;aCandidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildHeadingCandidates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&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;bCandidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildHeadingCandidates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;aCandidates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;bCandidates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&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;bSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bCandidates&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;bCompactSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bCandidates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;for &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;candidate&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;aCandidates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bCompactSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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="kc"&gt;false&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;Finally, I slimmed the comparison summary by comparing only cleaned items that survive both &lt;code&gt;isMeaningfulHeading()&lt;/code&gt; and &lt;code&gt;isEntityLike()&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;compareHeadings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myPageData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;competitorsData&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;myRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getComparisonItems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myPageData&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isMeaningfulHeading&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isEntityLike&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;compRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;competitorsData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;getComparisonItems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isMeaningfulHeading&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isEntityLike&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;sharedHeadings&lt;/span&gt; &lt;span class="o"&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;myPageOnlyHeadings&lt;/span&gt; &lt;span class="o"&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;competitorOnlyHeadings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;for &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;myItem&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;myRaw&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;hasMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;compRaw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;compItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;areHeadingsSimilar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;compItem&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasMatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;sharedHeadings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extractLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myItem&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;myPageOnlyHeadings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extractLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myItem&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for &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;compItem&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;compRaw&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;hasMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;myRaw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;myItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;areHeadingsSimilar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;myItem&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasMatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;competitorOnlyHeadings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extractLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compItem&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="na"&gt;sharedHeadings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uniqueList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sharedHeadings&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;myPageOnlyHeadings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uniqueList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myPageOnlyHeadings&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;competitorOnlyHeadings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uniqueList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;competitorOnlyHeadings&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After all three changes, the comparison output looked like this:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmkyivalqvpo0d435og2e.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmkyivalqvpo0d435og2e.png" alt="Enhanced comparison summary showing clean tool names without noise" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The difference is immediate: most of the noise is gone. The extracted lists now behave like actual entity sets, not raw headings. Shared tools are clean and consistent (&lt;code&gt;clickup&lt;/code&gt;, &lt;code&gt;asana&lt;/code&gt;, &lt;code&gt;wrike&lt;/code&gt;), and both &lt;code&gt;myPageOnlyHeadings&lt;/code&gt; and &lt;code&gt;competitorOnlyHeadings&lt;/code&gt; are dominated by recognizable product names rather than mixed content. There are no scores, no editorial sections, and no CSS fragments.&lt;/p&gt;

&lt;p&gt;The individual blog pages looked clean too, each tool clearly separated:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fha6afi854aov0nwzay1k.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fha6afi854aov0nwzay1k.png" alt="Clean extraction result from paymoapp.com after normalization" width="800" height="388"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0qacec7spf18l7itbzlk.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0qacec7spf18l7itbzlk.png" alt="Clean extraction result from zapier.com after normalization" width="800" height="401"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzjij2vckugt3kdp01pee.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzjij2vckugt3kdp01pee.png" alt="Clean extraction result from learn-dev-tools.blog" width="800" height="407"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foasn7uxnnct55ee1t0zw.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foasn7uxnnct55ee1t0zw.png" alt="Clean extraction result from project-management.com" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This output shows a clean separation between informational sections and the actual comparison block. I intentionally keep the full outline for each page instead of over-filtering it. The goal is not just extraction. Each individual page gives me a structural view of how competitors position their content: what sections they introduce, how they group tools, and what supporting topics they emphasize.&lt;/p&gt;

&lt;p&gt;The comparison result serves a different purpose. It compresses everything into a single snapshot: what overlaps, what I'm missing, and what competitors are covering. The two outputs are complementary. The outline helps me understand &lt;em&gt;how&lt;/em&gt; competitors structure their pages. The comparison helps me understand &lt;em&gt;what&lt;/em&gt; I should add or remove.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying the Actor on Apify
&lt;/h2&gt;

&lt;p&gt;Once the crawler was working locally, I deployed it so it could run on demand or on a schedule. Since the project already follows the Actor structure, deployment is one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apify push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The push itself went fine. But the first run failed. I checked the logs and saw that my inputs weren't present — which made sense once I thought about it. On the Apify platform, each Actor run gets its own isolated storage: a fresh dataset, key-value store, and request queue managed by the platform. The local &lt;code&gt;INPUT.json&lt;/code&gt; I had been using doesn't carry over after a push. I needed to define the input directly in the Console.&lt;/p&gt;

&lt;p&gt;After a few seconds, the Actor appears in your Apify Console under &lt;strong&gt;Actors&lt;/strong&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9egua6bvazchu6suhj8x.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9egua6bvazchu6suhj8x.png" alt="Apify Console home dashboard showing the deployed Actor" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open the Actor and set your input:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcbi1zesgjl3hy0xhfl1n.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcbi1zesgjl3hy0xhfl1n.png" alt="Apify Actor input configuration page" width="800" height="398"&gt;&lt;/a&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;"topic"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project management tools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"myPage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.learn-dev-tools.blog/best-legal-project-management-software/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"competitors"&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;"https://www.paymoapp.com/blog/project-management-software/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://project-management.com/top-10-project-management-software/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://zapier.com/blog/free-project-management-software"&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;p&gt;Then click &lt;strong&gt;Save &amp;amp; Start&lt;/strong&gt; to run the Actor. After the run completes, go to the &lt;strong&gt;Output&lt;/strong&gt; tab to preview results:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdh17q5o22kyx0clrli2g.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdh17q5o22kyx0clrli2g.png" alt="Actor output tab showing crawl results in table format" width="799" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also open the &lt;strong&gt;Dataset&lt;/strong&gt; to inspect the full JSON output — the &lt;a href="https://docs.apify.com/platform/storage/dataset" rel="noopener noreferrer"&gt;Apify Dataset storage docs&lt;/a&gt; explain how to export it in JSON, CSV, or Excel if you need it elsewhere.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd7shths9umgo9be949z4.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd7shths9umgo9be949z4.png" alt="Actor dataset view showing full JSON comparison output" width="799" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling runs
&lt;/h2&gt;

&lt;p&gt;I do competitor analysis monthly, so I set the Actor to run on a schedule instead of triggering it manually each time. Open your Actor in the Apify Console, click the three dots in the top-right corner, and select &lt;strong&gt;"Schedule Actor"&lt;/strong&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frk7zn2coqphorock3lx6.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frk7zn2coqphorock3lx6.jpg" alt="Apify Console showing the Schedule Actor option in the dropdown menu" width="798" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then configure the frequency:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffi3ewfdm57k1okcuxn2z.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffi3ewfdm57k1okcuxn2z.png" alt="Schedule Actor settings showing monthly run configuration" width="715" height="732"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running the crawler on a schedule turns it into a monitoring tool instead of a one-time analysis. Each run gives me an updated snapshot of new tools added by competitors, structural changes in their pages, and gaps that appear over time.&lt;/p&gt;

&lt;p&gt;If you want to make your Actor public and earn from it, follow the &lt;a href="https://docs.apify.com/platform/actors/publishing" rel="noopener noreferrer"&gt;Apify Actor publishing guide&lt;/a&gt; to list it on the &lt;a href="https://apify.com/store" rel="noopener noreferrer"&gt;Apify Store&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhancing capabilities
&lt;/h2&gt;

&lt;p&gt;The current version is reliable for extracting and comparing tools across different page structures. A few improvements that naturally follow from this setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handling dynamic pages: Some comparison pages rely heavily on JavaScript rendering. Switching to PlaywrightCrawler would allow extracting content that isn't present in the initial HTML.&lt;/li&gt;
&lt;li&gt;Improving resilience against structural drift: As sites update their layouts, selectors and assumptions can break. Adding fallback strategies or scoring multiple candidate nodes would make extraction more robust over time.&lt;/li&gt;
&lt;li&gt;Tracking changes over time: Instead of a one-time comparison, storing snapshots and detecting changes across runs would turn this into a proper competitor monitoring tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full code is available in the &lt;a href="https://github.com/Gunkev/seo-competitor-analysis-scraper" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; - the entity detection logic is the part most worth adapting if your content niche differs from comparison articles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;When I ran this against my Windows OS alternatives article, the output was immediately useful. Several tools my competitors were covering didn't appear anywhere on my page. Pop!OS was one of them — it showed up across multiple competitor pages while being completely absent from mine. I went back, added it, restructured a few sections, and added some supporting content based on what the comparison revealed.&lt;/p&gt;

&lt;p&gt;The key lesson wasn't about scraping. It was about the extraction layer. Raw headings are noisy and misleading. The real work is isolating actual content, stripping presentation artifacts, and making headings comparable across pages with completely different structures. Once that's solid, the comparison itself is straightforward.&lt;/p&gt;

&lt;p&gt;If you want to take it further, the natural next steps are switching to PlaywrightCrawler for JavaScript-heavy pages, adding a scheduled diff to track changes over time, and extending the entity detection logic to cover more content niches. The GitHub repository includes all the supporting functions referenced in this article.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>seo</category>
      <category>apify</category>
      <category>webscraping</category>
    </item>
    <item>
      <title>Localizing SaaS Applications: What Developers and Writers Get Wrong</title>
      <dc:creator>Gun</dc:creator>
      <pubDate>Wed, 10 Jun 2026 14:09:03 +0000</pubDate>
      <link>https://dev.to/gunkev/localizing-saas-applications-what-developers-and-writers-get-wrong-4286</link>
      <guid>https://dev.to/gunkev/localizing-saas-applications-what-developers-and-writers-get-wrong-4286</guid>
      <description>&lt;h2&gt;
  
  
  Localizing SaaS Applications: What Developers and Writers Get Wrong
&lt;/h2&gt;

&lt;p&gt;The demo goes live in Tokyo. Dates are formatted as &lt;code&gt;MM/DD/YYYY&lt;/code&gt;. Currency shows a dollar sign. One nav label reads “Einstellungen,” a German string left over from a previous sprint, jammed against a truncated button that was clearly designed for shorter English text. The Japanese enterprise prospect says nothing. They just close the tab.&lt;/p&gt;

&lt;p&gt;Nobody on the team saw it coming. The engineers shipped clean code, writers produced polished copy, translators did their jobs. And still, the product felt foreign, not because the words were wrong, but because the process was wrong.&lt;/p&gt;

&lt;p&gt;This is what localization failure actually looks like in SaaS: not a dramatic mistranslation scandal, but a quiet accumulation of small decisions made by people who never thought about global users at all. A hardcoded date format here. An untranslatable idiom in the onboarding copy there. A button width that never had room to grow.&lt;/p&gt;

&lt;p&gt;Localizing SaaS applications is one of the most misunderstood disciplines in product development. Most teams treat it as a translation project with a start date and an end date. It is neither. It is an architectural posture, a content discipline, and an ongoing product function, and the mistakes happen long before a single string ever reaches a linguist.&lt;/p&gt;

&lt;p&gt;This article breaks down exactly where developers and writers get it wrong, why it matters more than most teams realize, and what better looks like at every layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, Understand What You Are Actually Doing
&lt;/h2&gt;

&lt;p&gt;Before diagnosing the mistakes, it helps to clarify three terms that get used interchangeably in sprint planning docs and product roadmaps. They are not the same thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Translation&lt;/strong&gt; converts text from one language to another. It changes words.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Localization&lt;/strong&gt; (&lt;code&gt;l10n&lt;/code&gt;) adapts a product for a specific locale. It changes the experience: date formats, currency symbols, cultural references, legal copy, even which colors signal trust or danger in a given market. &lt;a href="https://learn.microsoft.com/en-us/globalization/localization/localization-overview" rel="noopener noreferrer"&gt;Microsoft’s localization guidance&lt;/a&gt; describes localization as the process of adapting a product to meet the language, cultural, and functional expectations of a specific market.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internationalization&lt;/strong&gt; (&lt;code&gt;i18n&lt;/code&gt;) is the engineering work done before any translation begins. It designs software so it can support multiple locales without requiring structural code changes for each one. Microsoft defines &lt;a href="https://learn.microsoft.com/en-us/globalization/methodology/software-internationalization" rel="noopener noreferrer"&gt;software internationalization&lt;/a&gt; as the planning and design work that makes software adaptable across languages, regions, and cultures.&lt;/p&gt;

&lt;p&gt;Confusing these three does not create a terminology problem, it creates an execution problem. Teams that skip straight to translation and wonder why scaling to a second language costs three times as much as the first are paying the price for skipping i18n. &lt;a href="https://crowdin.com/blog/saas-localization" rel="noopener noreferrer"&gt;Crowdin estimates&lt;/a&gt; that attempting SaaS localization without a proper i18n foundation can increase costs by 3-5x. Delaying i18n post-launch also creates long-term rework because the foundation always determines the ceiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Developers Get Wrong
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Hardcoding User-Facing Strings
&lt;/h3&gt;

&lt;p&gt;This is the most widespread and most expensive technical mistake in localizing SaaS applications. When developers embed text directly in component logic, every string becomes a surgical extraction problem later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User not found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;title="Settings"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiply that across a two-year-old codebase with four contributors and you are looking at weeks of archaeology before a translator sees a single word.&lt;/p&gt;

&lt;p&gt;The fix is foundational: externalize all user-facing text into resource files such as &lt;code&gt;.json&lt;/code&gt;, &lt;code&gt;.po&lt;/code&gt;, or &lt;code&gt;.xliff&lt;/code&gt;. Each string gets a key. Instead of &lt;code&gt;"Welcome back!"&lt;/code&gt; hardcoded in a component, you use:&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="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth.welcomeBack&lt;/span&gt;&lt;span class="dl"&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 component no longer cares what language it renders in. This sounds obvious. It gets skipped constantly, especially on small strings and error messages, which are exactly the strings that erode trust when they appear in English in the middle of a localized UI.&lt;/p&gt;

&lt;p&gt;This is also why standardized localization formats matter. Microsoft’s &lt;a href="https://learn.microsoft.com/en-us/globalization/localization/localization-file-formats" rel="noopener noreferrer"&gt;localization file-format guidance&lt;/a&gt; describes XLIFF as a format for exchanging localizable resources between tools, keeping translatable text separate from source files and allowing translators to work without touching the original code structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  String Concatenation
&lt;/h3&gt;

&lt;p&gt;This one is subtle and brutal. Developers write logic like this:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You have &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; items in your cart.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works fine in English. It fails in languages where grammatical structure differs from English word order. German, Japanese, Arabic, Russian, and many other languages cannot always preserve meaning when translators receive sentence fragments instead of complete sentences. Concatenated strings hand translators fragments with no linguistic context. They are essentially untranslatable.&lt;/p&gt;

&lt;p&gt;Microsoft’s &lt;a href="https://learn.microsoft.com/en-us/globalization/internationalization/message-formatting" rel="noopener noreferrer"&gt;message-formatting guidance&lt;/a&gt; makes the same point from a localization perspective: translators need enough control over placeholder order and placement because word order changes between languages.&lt;/p&gt;

&lt;p&gt;The correct approach uses ICU MessageFormat or an equivalent, giving translators a complete sentence template with named placeholders:&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;"cartItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You have {count, plural, one {# item} other {# items}} in your cart."&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;This also forces confrontation with the second concatenation trap: pluralization. English has two common forms. Other languages have different plural categories. The &lt;a href="https://cldr.unicode.org/index/cldr-spec/plural-rules" rel="noopener noreferrer"&gt;Unicode CLDR plural rules&lt;/a&gt; exist because languages do not share one universal singular/plural structure. ICU’s &lt;a href="https://unicode-org.github.io/icu/userguide/format_parse/messages/" rel="noopener noreferrer"&gt;MessageFormat&lt;/a&gt; uses those plural rules to keep plural logic inside the translatable message instead of scattering it across application code.&lt;/p&gt;

&lt;p&gt;The ICU plural category system gives translators the grammar rules they need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;zero&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;one&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;two&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;few&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;many&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;other&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If pluralization logic is hardcoded for English’s binary singular/plural pattern, other languages break silently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ignoring Layout Flexibility
&lt;/h3&gt;

&lt;p&gt;Translated text often expands or contracts, and W3C’s &lt;a href="https://www.w3.org/International/articles/article-text-size.en.html" rel="noopener noreferrer"&gt;text size in translation guidance&lt;/a&gt; shows that shorter English strings can expand significantly when translated into other languages. Arabic and Hebrew also read right-to-left. A UI designed exclusively around English string lengths will break in multiple directions at once.&lt;/p&gt;

&lt;p&gt;Buttons should not have fixed pixel widths. Navigation labels need room to breathe. Containers should be tested with pseudo-localization, a technique where placeholder text mimics the character width and encoding of target languages before real translation begins.&lt;/p&gt;

&lt;p&gt;Catching a layout collapse in pseudo-localization takes minutes. Catching it in production, in a Japanese enterprise account, takes weeks of engineering time and an apology nobody wants to write.&lt;/p&gt;

&lt;p&gt;RTL support deserves its own flag in the i18n roadmap. Microsoft’s &lt;a href="https://learn.microsoft.com/en-us/globalization/methodology/software-internationalization" rel="noopener noreferrer"&gt;software internationalization guidance&lt;/a&gt; explicitly flags bidirectional writing systems, such as Arabic and Hebrew, as something products should account for early.&lt;/p&gt;

&lt;p&gt;CSS logical properties make this significantly less painful when built in from the start rather than retrofitted after the fact:&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;margin-inline-start&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="nt"&gt;rem&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;padding-inline-end&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="nt"&gt;rem&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is safer than hardcoding layout rules like:&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;margin-left&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="nt"&gt;rem&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;padding-right&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="nt"&gt;rem&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Treating Locale as a Display-Layer Problem
&lt;/h3&gt;

&lt;p&gt;Locale is not a stylesheet. It affects business logic.&lt;/p&gt;

&lt;p&gt;The date &lt;code&gt;05/06/07&lt;/code&gt; can mean different things depending on the locale. Number separators vary by country. Pricing display norms differ by market. Address field structures differ by country. Phone number formats differ by region.&lt;/p&gt;

&lt;p&gt;None of this is a translation issue. All of it needs to be handled at the data and logic layer, using locale-aware libraries rather than format strings assembled at render time.&lt;/p&gt;

&lt;p&gt;The JavaScript &lt;a href="https://www.w3.org/International/i18n-drafts/articles/intl/index.en" rel="noopener noreferrer"&gt;&lt;code&gt;Intl&lt;/code&gt; API&lt;/a&gt; exists for this reason. It provides locale-sensitive formatting for dates, times, numbers, lists, and currencies, so applications do not have to rely on fragile manual formatting rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Writers and Content Teams Get Wrong
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Writing Source Copy That Cannot Be Localized
&lt;/h3&gt;

&lt;p&gt;Technical writers and UX copywriters set the ceiling for every language that follows. If the English source is idiomatic, assumes Western context, or relies on wordplay, the localized versions will either sound awkward or cost a fortune to adapt properly.&lt;/p&gt;

&lt;p&gt;Phrases like “let’s get the ball rolling,” “out of the box,” or “circle back” are invisible to native English speakers and genuinely difficult to translate for most linguists without losing meaning entirely. The same goes for culturally embedded metaphors, puns in feature names, and humor that depends on English phonetics.&lt;/p&gt;

&lt;p&gt;The fix is straightforward: write source copy with localization in mind from the first draft. Use plain language. Favor direct, complete sentences over headline fragments. Fragments often drop grammatical cues that translators need to determine gender, tense, and case. Avoid idioms unless you are willing to budget for creative adaptation in every target locale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sending Strings Without Context
&lt;/h3&gt;

&lt;p&gt;This is one of the most consistently cited mistakes among localization professionals. When a translator receives the string “Charge” with no additional information, they cannot tell if it means a financial transaction, a battery level, or a database write operation. The string “Check-in” could be a button, a status indicator, or a noun phrase.&lt;/p&gt;

&lt;p&gt;Without a screenshot, a usage note, or a character limit, the translator is guessing, and a wrong guess on a billing CTA has direct revenue consequences.&lt;/p&gt;

&lt;p&gt;Every string handed to a translation workflow should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;where it appears in the UI&lt;/li&gt;
&lt;li&gt;what action or state it describes&lt;/li&gt;
&lt;li&gt;any character limit constraints&lt;/li&gt;
&lt;li&gt;a screenshot if one exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This also reduces retranslation cost. Microsoft’s &lt;a href="https://learn.microsoft.com/en-us/globalization/localization/localization-overview" rel="noopener noreferrer"&gt;localization overview&lt;/a&gt; notes that source errors, inconsistent terminology, and unclear materials can slow translation and create additional localization work after the source text changes.&lt;/p&gt;

&lt;p&gt;Every major translation management system, including Lokalise, Phrase, and Crowdin, supports contextual metadata. The problem is not tooling availability. It is the discipline of populating that metadata before the handoff, not after the rework comes back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treating Localization as a One-Time Release Task
&lt;/h3&gt;

&lt;p&gt;SaaS products ship continuously. Features are added weekly. Error messages are revised. Onboarding copy evolves with every A/B test. When localization is treated as a project milestone rather than a continuous workflow, translated versions fall behind the English source almost immediately.&lt;/p&gt;

&lt;p&gt;Users in localized markets end up with a mixed-language interface: their language for established features, English for everything new. This is not a small cosmetic issue. It signals, loudly, that they are second-class users.&lt;/p&gt;

&lt;p&gt;The answer is continuous localization: integrating translation workflows directly into the CI/CD pipeline so that new or updated strings trigger translation automatically when a developer pushes.&lt;/p&gt;

&lt;p&gt;Git integrations in tools like Crowdin, Phrase, and Transifex make this operational rather than aspirational. &lt;a href="https://github.com/crowdin/github-action" rel="noopener noreferrer"&gt;Crowdin’s GitHub Action&lt;/a&gt; can upload source files, download translations, and create pull requests. &lt;a href="https://support.phrase.com/hc/en-us/articles/5784125562012-GitHub-Strings" rel="noopener noreferrer"&gt;Phrase&lt;/a&gt; and &lt;a href="https://help.transifex.com/en/articles/6265125-github-installation-and-configuration" rel="noopener noreferrer"&gt;Transifex&lt;/a&gt; also document GitHub integrations for synchronizing localization files between repositories and translation workflows.&lt;/p&gt;

&lt;p&gt;When the engineering release cycle and the localization cycle run in parallel, you get simultaneous global launches. When they are decoupled, you get a perpetually outdated product for your non-English users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where SEO Enters the Localization Workflow
&lt;/h2&gt;

&lt;p&gt;Localized product content also needs localized search signals. If two pages target different language or regional versions of the same content, the implementation should help search engines understand the relationship between those versions.&lt;/p&gt;

&lt;p&gt;For multilingual SaaS sites, this usually means using separate localized URLs, translated metadata, and correct &lt;code&gt;hreflang&lt;/code&gt; annotations. Google’s documentation explains that &lt;a href="https://developers.google.com/search/docs/specialty/international/localized-versions" rel="noopener noreferrer"&gt;&lt;code&gt;hreflang&lt;/code&gt; helps it understand localized variations of the same page&lt;/a&gt;, so users can be served the version that best matches their language or region.&lt;/p&gt;

&lt;p&gt;This matters because localization does not stop at the interface. Pricing pages, documentation, onboarding flows, help-center articles, and product-led SEO pages all become part of the localized user journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Structural Problem Underneath All of It
&lt;/h2&gt;

&lt;p&gt;Most of the mistakes above share a common origin: localization has no owner until it becomes a crisis.&lt;/p&gt;

&lt;p&gt;In the design sprint, it is nobody’s job to verify that the component supports RTL. In the content review, it is nobody’s job to flag an untranslatable idiom. In the engineering standup, it is nobody’s job to confirm that a new error message has been externalized and keyed.&lt;/p&gt;

&lt;p&gt;So these gaps accumulate quietly, sprint after sprint, until the Tokyo demo goes sideways and suddenly it is everyone’s problem at once.&lt;/p&gt;

&lt;p&gt;The organizations that scale globally without painful rework have done one structural thing differently: they embedded localization ownership into product development before it became urgent. That means a localization lead with a seat at the product table, a shared glossary enforced at the TMS level, and a standing checklist: no new feature ships without an i18n review.&lt;/p&gt;

&lt;h2&gt;
  
  
  It Is a Systems Problem, Not a Language Problem
&lt;/h2&gt;

&lt;p&gt;Localizing SaaS applications looks like a language challenge from the outside. From the inside, it is a systems design challenge with a language-shaped surface.&lt;/p&gt;

&lt;p&gt;The translator cannot fix a hardcoded string. The writer cannot untangle a concatenated sentence after the fact. The product manager cannot retroactively build RTL support into a layout that was never designed for it.&lt;/p&gt;

&lt;p&gt;Every one of these problems is seeded at the source in architecture decisions, content choices, and workflow assumptions made by people who were not thinking about global users at the time.&lt;/p&gt;

&lt;p&gt;The good news is that none of these mistakes requires heroics to prevent. Externalize your strings early. Write source copy in plain, translatable language. Give translators context, not just words. Build localization into your release pipeline rather than scheduling it as a separate sprint. Assign ownership before the crisis arrives.&lt;/p&gt;

&lt;p&gt;Do those things, and the translation itself, the part most people treat as the whole job, becomes the easy part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/globalization/localization/localization-overview" rel="noopener noreferrer"&gt;Microsoft: Localize your product&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/globalization/methodology/software-internationalization" rel="noopener noreferrer"&gt;Microsoft: Software internationalization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/globalization/localization/localization-file-formats" rel="noopener noreferrer"&gt;Microsoft: Localization file formats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/globalization/internationalization/message-formatting" rel="noopener noreferrer"&gt;Microsoft: Message formatting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/International/articles/article-text-size.en.html" rel="noopener noreferrer"&gt;W3C: Text size in translation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/International/i18n-drafts/articles/intl/index.en" rel="noopener noreferrer"&gt;W3C: JavaScript internationalization API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cldr.unicode.org/index/cldr-spec/plural-rules" rel="noopener noreferrer"&gt;Unicode CLDR: Plural rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unicode-org.github.io/icu/userguide/format_parse/messages/" rel="noopener noreferrer"&gt;ICU: MessageFormat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/search/docs/specialty/international/localized-versions" rel="noopener noreferrer"&gt;Google Search Central: Localized versions of pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://crowdin.com/blog/saas-localization" rel="noopener noreferrer"&gt;Crowdin: SaaS localization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/crowdin/github-action" rel="noopener noreferrer"&gt;Crowdin GitHub Action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.phrase.com/hc/en-us/articles/5784125562012-GitHub-Strings" rel="noopener noreferrer"&gt;Phrase GitHub integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://help.transifex.com/en/articles/6265125-github-installation-and-configuration" rel="noopener noreferrer"&gt;Transifex GitHub integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>localization</category>
      <category>internationalization</category>
      <category>i18n</category>
      <category>tms</category>
    </item>
    <item>
      <title>How to Analyze a CSV File with Python and Pandas</title>
      <dc:creator>Gun</dc:creator>
      <pubDate>Fri, 15 May 2026 18:17:23 +0000</pubDate>
      <link>https://dev.to/gunkev/how-to-analyze-a-csv-file-with-python-and-pandas-32o5</link>
      <guid>https://dev.to/gunkev/how-to-analyze-a-csv-file-with-python-and-pandas-32o5</guid>
      <description>&lt;p&gt;CSV files show up in almost every business workflow. You may export orders from an ecommerce platform, download leads from a CRM, receive finance data from another team, or pull product usage data from an internal dashboard.&lt;br&gt;
The challenge is that a raw CSV file rarely answers a question directly. It may contain duplicate rows, missing values, inconsistent column names, and dates stored in a format that makes time analysis difficult.&lt;br&gt;
In this tutorial, you will work through a realistic sales analysis project. Imagine you received an ecommerce order export and need to answer one practical question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What can this CSV tell us about revenue by product, category, country, and month?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You will start with a raw CSV file and finish with cleaned data, useful summaries, visualizations, and exported result files.&lt;br&gt;
By the end, you will have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loaded a CSV file into Pandas&lt;/li&gt;
&lt;li&gt;Inspected rows, columns, data types, and missing values&lt;/li&gt;
&lt;li&gt;Cleaned column names, duplicates, missing values, and data types&lt;/li&gt;
&lt;li&gt;Created a revenue column&lt;/li&gt;
&lt;li&gt;Used filtering, sorting, and &lt;code&gt;groupby()&lt;/code&gt; to answer business questions&lt;/li&gt;
&lt;li&gt;Created a category bar chart and monthly line chart&lt;/li&gt;
&lt;li&gt;Exported cleaned data and summary CSV files
## Prerequisites&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should know basic Python syntax, including variables, function calls, imports, and running scripts or notebooks. You do not need previous Pandas experience.&lt;br&gt;
You can follow along in Jupyter Notebook, Google Colab, VS Code, or a regular Python script. A notebook gives you the easiest learning experience because you can inspect each result as you go.&lt;/p&gt;

&lt;p&gt;Install Pandas and Matplotlib:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pip install pandas matplotlib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you use Conda, install from &lt;code&gt;conda-forge&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conda install -c conda-forge pandas matplotlib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now import the libraries:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import pandas as pd
import matplotlib.pyplot as plt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Check your Pandas version:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(pd.__version__)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You may see output like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3.0.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This tutorial uses modern Pandas practices. You will avoid chained assignment, and you will not use &lt;code&gt;inplace=True&lt;/code&gt; as the default way to update data. Direct assignment usually reads better and works well with current Pandas behavior.&lt;/p&gt;

&lt;p&gt;If Python raises &lt;code&gt;ModuleNotFoundError: No module named 'pandas'&lt;/code&gt;, your editor and terminal may use different Python environments. In VS Code, check the selected interpreter. In Jupyter, check the active kernel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the Sales CSV File
&lt;/h2&gt;

&lt;p&gt;You will use one dataset throughout this tutorial: a small ecommerce sales export. Keeping the dataset small lets you see every row, but the workflow matches what you would do with a larger real-world CSV. Either use a sale csv file you have, create a sample one or &lt;a href="https://drive.google.com/file/d/1KcML2xBXG5kBX4yoWRdwI-cs1LHd5vo8/view?usp=sharing" rel="noopener noreferrer"&gt;&lt;strong&gt;use this example&lt;/strong&gt;&lt;/a&gt; to follow along&lt;/p&gt;

&lt;p&gt;The dataset includes the columns you would expect in a sales export: order ID, order date, product, category, quantity, unit price, customer country, and payment method. It also includes a few issues on purpose. One row appears twice, one order has no date, one order has no category, and one order has no unit price.&lt;/p&gt;

&lt;p&gt;Those issues are useful. They let you practice the kind of judgment you need when analyzing real CSV files. &lt;/p&gt;

&lt;h2&gt;
  
  
  Load the CSV File With Pandas
&lt;/h2&gt;

&lt;p&gt;Load the file with &lt;code&gt;pd.read_csv()&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df = pd.read_csv(
    "sales_data.csv",
    parse_dates=["Order Date"]
)

print(df.head())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&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%2F3b2ccnvfu65p1h5bd46n.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%2F3b2ccnvfu65p1h5bd46n.png" width="800" height="92"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This code creates a DataFrame named &lt;code&gt;df&lt;/code&gt;. A DataFrame is Pandas’ table structure: rows represent records, and columns represent fields.&lt;br&gt;
The &lt;code&gt;parse_dates=["Order Date"]&lt;/code&gt; argument matters because you will analyze monthly revenue later. If you load dates as plain text, you create extra work for yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understand File Paths&lt;/strong&gt;&lt;br&gt;
The path &lt;code&gt;"sales_data.csv"&lt;/code&gt; is a relative path. It means Python should look for the file in the folder where your code runs.&lt;br&gt;
If your file lives somewhere else, use an absolute path. On Windows, that might look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df = pd.read_csv(
    r"C:\Users\YourName\Downloads\sales_data.csv",
    parse_dates=["Order Date"]
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For this project, I would encourage you to use relative paths. They make your analysis easier to move between folders, machines, and teammates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Useful&lt;/strong&gt; &lt;code&gt;**read_csv()**&lt;/code&gt; &lt;strong&gt;Options&lt;/strong&gt;&lt;br&gt;
You do not need many options for this sample file, but real CSV files often need extra instructions.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df = pd.read_csv(
    "sales_data.csv",
    sep=",",
    encoding="utf-8",
    parse_dates=["Order Date"],
    na_values=["", "NA", "N/A", "null"]
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here is the practical rule:&lt;br&gt;
Use &lt;code&gt;sep&lt;/code&gt; when the delimiter is not a comma. Use &lt;code&gt;encoding&lt;/code&gt; when the file fails to decode. Use &lt;code&gt;na_values&lt;/code&gt; when the source system uses custom labels for missing values. Use &lt;code&gt;parse_dates&lt;/code&gt; when a column should behave like a date.&lt;/p&gt;

&lt;p&gt;You will see &lt;code&gt;usecols&lt;/code&gt;, &lt;code&gt;dtype&lt;/code&gt;, and &lt;code&gt;chunksize&lt;/code&gt; later when you adapt the workflow to larger files.&lt;br&gt;
If you get &lt;code&gt;FileNotFoundError&lt;/code&gt;, Pandas cannot find the file from the current working folder. Run &lt;code&gt;print(Path.cwd())&lt;/code&gt;, move the CSV there, or use a full path.&lt;br&gt;
If the file loads as one giant column, try a different delimiter such as &lt;code&gt;sep=";"&lt;/code&gt; or &lt;code&gt;sep="\t"&lt;/code&gt;.&lt;br&gt;
If Pandas raises &lt;code&gt;UnicodeDecodeError&lt;/code&gt;, try &lt;code&gt;encoding="latin1"&lt;/code&gt; or ask the data owner for the file encoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect the Data Before You Clean It
&lt;/h2&gt;

&lt;p&gt;Before you clean anything, inspect the dataset. This step protects you from making lazy assumptions.&lt;/p&gt;

&lt;p&gt;Start with the first rows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(df.head())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then check the last rows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(df.tail())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;head()&lt;/code&gt; confirms that the file loaded as expected. &lt;code&gt;tail()&lt;/code&gt; helps you catch issues near the bottom of the file. In this dataset, the last row repeats order &lt;code&gt;1008&lt;/code&gt;, which hints at a duplicate.&lt;/p&gt;

&lt;p&gt;Expected result for tail&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%2F9q7y2z9p5wsrr808z3pv.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%2F9q7y2z9p5wsrr808z3pv.png" width="800" height="91"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check the size:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(df.shape)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(16, 8)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The raw dataset has 16 rows and 8 columns.&lt;/p&gt;

&lt;p&gt;Inspect the column names:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(df.columns)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&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%2Focvhb7vi6doe6twk470e.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%2Focvhb7vi6doe6twk470e.png" width="800" height="72"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The names make sense to a human, but they are awkward in code because they contain spaces and mixed capitalization.&lt;/p&gt;

&lt;p&gt;Now use &lt;code&gt;info()&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df.info()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output will look similar to this:&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%2F2sg1jbu3qb3lxidxmvgc.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%2F2sg1jbu3qb3lxidxmvgc.png" width="790" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This tells you where the main data quality issues are. You have missing values in &lt;code&gt;Order Date&lt;/code&gt;, &lt;code&gt;Category&lt;/code&gt;, and &lt;code&gt;Unit Price&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;describe()&lt;/code&gt; to inspect numeric columns:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(df.describe())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Look for values that violate common business rules: negative quantities, zero prices, or extreme outliers. In this small file, the numeric ranges look reasonable.&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%2Fvz0elaswa0yf5aij9hr2.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%2Fvz0elaswa0yf5aij9hr2.png" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, check exact data types:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(df.dtypes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Your text-column dtypes may vary slightly by Pandas version. &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%2Fym3lquh18y4g6zj2nshm.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%2Fym3lquh18y4g6zj2nshm.png" width="490" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Focus on the important question: can Pandas treat dates as dates and numbers as numbers?&lt;br&gt;
If &lt;code&gt;Order Date&lt;/code&gt; appears as &lt;code&gt;object&lt;/code&gt; or &lt;code&gt;str&lt;/code&gt;, Pandas did not parse it as a date. Check that &lt;code&gt;parse_dates=["Order Date"]&lt;/code&gt; matches the column name exactly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean the CSV Data
&lt;/h2&gt;

&lt;p&gt;Now clean the file so you can trust the analysis. Cleaning does not mean forcing the data to look perfect. It means making clear, documented decisions that support your questions. We will start with the column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clean Column Names&lt;/strong&gt;&lt;br&gt;
Clean names early because every later step uses these names.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df.columns = (
    df.columns
    .str.strip()
    .str.lower()
    .str.replace(" ", "_")
)

print(df.columns)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&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%2Fdpvpxuixo7zyn2bvibps.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%2Fdpvpxuixo7zyn2bvibps.png" width="800" height="72"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Index(['order_id', 'order_date', 'product', 'category', 'quantity',
       'unit_price', 'customer_country', 'payment_method'],
      dtype='object')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now your code can use &lt;code&gt;df["unit_price"]&lt;/code&gt; instead of &lt;code&gt;df["Unit Price"]&lt;/code&gt;.&lt;br&gt;
This may look cosmetic, but it is practical. Clean column names reduce typing errors and make your code easier to scan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Count Missing Values&lt;/strong&gt;&lt;br&gt;
Count missing values after cleaning the column names:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(df.isna().sum())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&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%2Fsdobo2zrbef7gdqlw3bv.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%2Fsdobo2zrbef7gdqlw3bv.png" width="412" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This output gives you three separate cleaning decisions. A missing date, missing category, and missing price do not mean the same thing. Treating them the same way would create weak analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remove Duplicate Rows&lt;/strong&gt;&lt;br&gt;
Check exact duplicates:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(df.duplicated().sum())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Remove the duplicate row:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df = df.drop_duplicates()

print(df.shape)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(15, 8)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You now have 15 rows.&lt;br&gt;
This is safe here because the duplicate row matches exactly. In a real dataset, duplicate IDs with different values require investigation, so do not delete those blindly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handle Missing Dates&lt;/strong&gt;&lt;br&gt;
We want monthly revenue, so rows without dates cannot support one of the main goals. Drop rows where &lt;code&gt;order_date&lt;/code&gt; is missing:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df = df.dropna(subset=["order_date"])

print(df["order_date"].isna().sum())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This choice is specific to our sale file. If your analysis did not use dates, you might keep the row.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fill Missing Categories&lt;/strong&gt;&lt;br&gt;
A missing category should not erase a valid sale. Keep the row and label the missing value as unknown:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df["category"] = df["category"].fillna("Unknown")

print(df["category"].value_counts(dropna=False))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output should show one &lt;code&gt;Unknown&lt;/code&gt; category.&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%2F14mqf8tn9fzah5dq4mnd.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%2F14mqf8tn9fzah5dq4mnd.png" width="418" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This lets you include the revenue while still exposing the data-quality issue. That is usually better than hiding the row or pretending you know the category.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fill Missing Unit Prices&lt;/strong&gt;&lt;br&gt;
A missing price affects revenue, so you need a reasonable rule. For this dataset, fill the missing price with the median price of the same product:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df["unit_price"] = df["unit_price"].fillna(
    df.groupby("product")["unit_price"].transform("median")
)

print(df["unit_price"].isna().sum())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The important method here is &lt;code&gt;transform("median")&lt;/code&gt;. It calculates a median unit price for each product and returns values aligned to the original rows. That alignment lets Pandas fill the missing price with the right product-level median.&lt;/p&gt;

&lt;p&gt;This beats a global median because product prices are not interchangeable. A monitor price and a cable price should not influence a missing notebook price.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confirm and Fix Data Types&lt;/strong&gt;&lt;br&gt;
Check data types again:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(df.dtypes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Convert text columns to Pandas string type:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df = df.astype({
    "product": "string",
    "category": "string",
    "customer_country": "string",
    "payment_method": "string"
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For this sample file, &lt;code&gt;quantity&lt;/code&gt; and &lt;code&gt;unit_price&lt;/code&gt; should already be numeric. For real files, convert numeric columns explicitly when needed:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df["quantity"] = pd.to_numeric(df["quantity"], errors="coerce")
df["unit_price"] = pd.to_numeric(df["unit_price"], errors="coerce")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then check whether conversion introduced missing values:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(df[["quantity", "unit_price"]].isna().sum())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Use &lt;code&gt;errors="coerce"&lt;/code&gt; when you want invalid numeric text to become missing values. That makes the problem visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a Revenue Column&lt;/strong&gt;&lt;br&gt;
Create the metric you will use for analysis:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df["total_sales"] = df["quantity"] * df["unit_price"]

print(df[["order_id", "quantity", "unit_price", "total_sales"]].head())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&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%2F5wgdenfbc18ymwvzsn8u.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%2F5wgdenfbc18ymwvzsn8u.png" width="661" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, the dataset has moved from raw order records to analysis-ready sales data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cleaning Checkpoint&lt;/strong&gt;&lt;br&gt;
Before analysis, run one quick checkpoint:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(df.shape)
print(df.isna().sum())
print(df.dtypes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should see 14 rows after removing one duplicate and one row without a date. You should also see no missing values in the fields needed for this project.&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%2Ferwwgq6xubmq3kjp8gby.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%2Ferwwgq6xubmq3kjp8gby.png" width="654" height="616"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This checkpoint separates cleaning from analysis. You do not want to discover data-quality problems halfway through a revenue summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analyze Sales With &lt;code&gt;groupby()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;From now you can start answering business questions. The main tool in this section is &lt;code&gt;groupby()&lt;/code&gt;. In plain English, &lt;code&gt;groupby()&lt;/code&gt; lets you split rows into groups, calculate something for each group, and combine the results into a summary table.&lt;/p&gt;

&lt;p&gt;For example, when you group by category and sum revenue, Pandas does this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Splits rows into category groups.&lt;/li&gt;
&lt;li&gt;Adds &lt;code&gt;total_sales&lt;/code&gt; inside each category.&lt;/li&gt;
&lt;li&gt;Returns one row per category.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Calculate Total Revenue&lt;/strong&gt;&lt;br&gt;
Start with the headline metric:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total_revenue = df["total_sales"].sum()

print(f"Total revenue: ${total_revenue:,.2f}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total revenue: $1,333.57
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is the total revenue after cleaning. It excludes the duplicate row and the row without an order date.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find Revenue by Category&lt;/strong&gt;&lt;br&gt;
Group by category:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;category_sales = (
    df.groupby("category", as_index=False)["total_sales"]
    .sum()
    .sort_values("total_sales", ascending=False)
)

print(category_sales)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&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%2F89n2kkt5zc8qahngsdd1.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%2F89n2kkt5zc8qahngsdd1.png" width="499" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;as_index=False&lt;/code&gt; keeps &lt;code&gt;category&lt;/code&gt; as a normal column instead of turning it into the index. For beginner analysis and CSV exports, that format is easier to read and reuse. Furniture leads revenue, even though it has fewer orders than some other categories. High unit prices can matter more than order count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find Top Products&lt;/strong&gt;&lt;br&gt;
Now summarize by product:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;product_sales = (
    df.groupby("product", as_index=False)
    .agg(
        total_sales=("total_sales", "sum"),
        units_sold=("quantity", "sum"),
        orders=("order_id", "count")
    )
    .sort_values("total_sales", ascending=False)
)

print(product_sales)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&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%2Fu6dqsvvemofss53lmk2k.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%2Fu6dqsvvemofss53lmk2k.png" width="742" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This table is more useful than a single revenue ranking. It shows revenue, units sold, and order count together. The Monitor generated the most revenue. USB-C Cable sold the most units. Those are different types of performance, and a good analyst keeps them separate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find Revenue by Country&lt;/strong&gt;&lt;br&gt;
Group by customer country:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;country_sales = (
    df.groupby("customer_country", as_index=False)["total_sales"]
    .sum()
    .sort_values("total_sales", ascending=False)
)

print(country_sales)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&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%2Fbznbndwjjttmi0g469k4.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%2Fbznbndwjjttmi0g469k4.png" width="735" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The United States contributes the most revenue in this sample. In a real business, this kind of table can support regional reporting, marketing decisions, or logistics planning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analyze Monthly Revenue&lt;/strong&gt;&lt;br&gt;
Create a month column from &lt;code&gt;order_date&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df["order_month"] = df["order_date"].dt.to_period("M").astype("string")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;.dt&lt;/code&gt; accessor lets you work with datetime values. &lt;code&gt;to_period("M")&lt;/code&gt; converts each date to its month, such as &lt;code&gt;2026-03&lt;/code&gt;. That gives you a clean monthly grouping key.&lt;br&gt;
Now summarize revenue by month:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;monthly_sales = (
    df.groupby("order_month", as_index=False)["total_sales"]
    .sum()
    .sort_values("order_month")
)

print(monthly_sales)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&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%2F0udafi8lolv52but5amp.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%2F0udafi8lolv52but5amp.png" width="756" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Revenue rises in March and April because the dataset includes higher-priced furniture orders in those months.&lt;br&gt;
If &lt;code&gt;.dt&lt;/code&gt; raises an error, &lt;code&gt;order_date&lt;/code&gt; is not a datetime column. Convert it with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df["order_date"] = pd.to_datetime(df["order_date"], errors="coerce")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then check &lt;code&gt;df["order_date"].isna().sum()&lt;/code&gt; before continuing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Filter High-Value Orders&lt;/strong&gt;&lt;br&gt;
Use filtering to inspect the rows behind your summary tables.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;high_value_orders = df[df["total_sales"] &amp;gt;= 100].sort_values(
    "total_sales",
    ascending=False
)

print(high_value_orders[["order_id", "product", "customer_country", "total_sales"]])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&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%2F4q3numhh9l0ag5wt9px6.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%2F4q3numhh9l0ag5wt9px6.png" width="800" height="143"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only a few orders drive a large share of revenue. This is the kind of detail that summary tables can hide.&lt;br&gt;
For multiple conditions, wrap each condition in parentheses:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;us_high_value_orders = df[
    (df["customer_country"] == "United States") &amp;amp;
    (df["total_sales"] &amp;gt;= 100)
]

print(us_high_value_orders[["order_id", "product", "total_sales"]])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Use &lt;code&gt;&amp;amp;&lt;/code&gt; for “and” and &lt;code&gt;|&lt;/code&gt; for “or”. Python’s &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;or&lt;/code&gt; do not work for Pandas Series filters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualize the Results
&lt;/h2&gt;

&lt;p&gt;You should not chart everything. Chart the results that become easier to understand visually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Category Revenue Bar Chart&lt;/strong&gt;&lt;br&gt;
Use a bar chart for category comparison:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;category_sales.plot(
    kind="bar",
    x="category",
    y="total_sales",
    legend=False,
    title="Revenue by Category"
)

plt.xlabel("Category")
plt.ylabel("Revenue")
plt.tight_layout()
plt.show()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The chart should show Furniture as the highest-revenue category. A stakeholder could understand that pattern faster from the chart than from the table.&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%2Fjkf615qz1uq2bgnpdgx1.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%2Fjkf615qz1uq2bgnpdgx1.png" width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monthly Revenue Line Chart&lt;/strong&gt;&lt;br&gt;
Use a line chart for time trends:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;monthly_sales.plot(
    kind="line",
    x="order_month",
    y="total_sales",
    marker="o",
    legend=False,
    title="Monthly Revenue"
)

plt.xlabel("Month")
plt.ylabel("Revenue")
plt.tight_layout()
plt.show()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The line should rise in March and April. In a larger dataset, this chart would help you identify seasonality, campaign effects, or unexpected drops.&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%2Frow1c9eks5u9q32fc2tb.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%2Frow1c9eks5u9q32fc2tb.png" width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If charts do not appear, make sure Matplotlib is installed and call &lt;code&gt;plt.show()&lt;/code&gt;. In Jupyter notebooks, &lt;code&gt;%matplotlib inline&lt;/code&gt; can help, but do not use that line in a regular Python script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working With Larger CSV Files
&lt;/h2&gt;

&lt;p&gt;This tutorial uses a small file so you can focus on the workflow. Real CSV files may contain hundreds of thousands or millions of rows. You can still use Pandas, but you should load the data more carefully.&lt;br&gt;
If you only need some columns, use &lt;code&gt;usecols&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;columns_to_load = [
    "Order ID",
    "Order Date",
    "Product",
    "Category",
    "Quantity",
    "Unit Price",
    "Customer Country"
]

large_df = pd.read_csv(
    "sales_data.csv",
    usecols=columns_to_load,
    parse_dates=["Order Date"]
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This reduces memory use and keeps your analysis focused.&lt;br&gt;
If you know the schema, set data types while loading:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;large_df = pd.read_csv(
    "sales_data.csv",
    usecols=columns_to_load,
    parse_dates=["Order Date"],
    dtype={
        "Product": "string",
        "Category": "string",
        "Customer Country": "string",
        "Quantity": "int64",
        "Unit Price": "float64"
    }
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you are unsure about types, load a small sample first:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sample_df = pd.read_csv("sales_data.csv", nrows=1000)

print(sample_df.dtypes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For very large files, read in chunks:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;revenue_by_country = {}

for chunk in pd.read_csv(
    "sales_data.csv",
    parse_dates=["Order Date"],
    chunksize=5
):
    chunk.columns = (
        chunk.columns
        .str.strip()
        .str.lower()
        .str.replace(" ", "_")
    )

    chunk = chunk.dropna(subset=["order_date", "unit_price"])
    chunk["total_sales"] = chunk["quantity"] * chunk["unit_price"]

    chunk_summary = chunk.groupby("customer_country")["total_sales"].sum()

    for country, revenue in chunk_summary.items():
        revenue_by_country[country] = revenue_by_country.get(country, 0) + revenue

print(revenue_by_country)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F6klc5zgws13edyt7nvqr.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%2F6klc5zgws13edyt7nvqr.png" width="800" height="33"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The example uses &lt;code&gt;chunksize=5&lt;/code&gt; only because the sample file is tiny. For real files, try &lt;code&gt;chunksize=50_000&lt;/code&gt; or &lt;code&gt;100_000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pandas also supports &lt;code&gt;dtype_backend="pyarrow"&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df_arrow = pd.read_csv(
    "sales_data.csv",
    dtype_backend="pyarrow"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Treat this as optional. It can help some larger workflows, but it may require PyArrow and does not need to be your first optimization.&lt;br&gt;
Pandas is enough for many CSV analysis tasks. If your data outgrows memory or you need fast SQL over local files, look at DuckDB. If you want a fast DataFrame library, consider Polars. If you need larger-than-memory workflows with familiar Pandas-like patterns, consider Dask. If many people need shared reporting, use a database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Export Your Cleaned Data and Summaries
&lt;/h2&gt;

&lt;p&gt;Now finish the project by saving your work.&lt;/p&gt;

&lt;p&gt;Save the cleaned dataset:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df.to_csv("cleaned_sales_data.csv", index=False)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Save the summary tables:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;category_sales.to_csv("category_sales.csv", index=False)
product_sales.to_csv("product_sales.csv", index=False)
country_sales.to_csv("country_sales.csv", index=False)
monthly_sales.to_csv("monthly_sales.csv", index=False)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Use &lt;code&gt;index=False&lt;/code&gt; because the DataFrame index does not contain business information. If you omit it, Pandas writes the index to the file. When you load that file later, you may see an unwanted &lt;code&gt;Unnamed: 0&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;Confirm that your output files exist:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output_files = [
    "cleaned_sales_data.csv",
    "category_sales.csv",
    "product_sales.csv",
    "country_sales.csv",
    "monthly_sales.csv"
]

for file_name in output_files:
    print(file_name, Path(file_name).exists())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Expected output:&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%2Fzp1sxeeh59sezic7are1.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%2Fzp1sxeeh59sezic7are1.png" width="748" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You now have files you can share, inspect in a spreadsheet, or use in another analysis step.&lt;br&gt;
If Pandas raises a permission error, close the file in Excel or any other program that may have locked it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes When Analyzing CSV Files
&lt;/h2&gt;

&lt;p&gt;Most beginner CSV problems come from file setup, data types, and cleaning decisions.&lt;/p&gt;

&lt;p&gt;If Pandas cannot find your file, check your working folder:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;print(Path.cwd())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If the CSV loads as one giant column, specify the delimiter:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df = pd.read_csv("sales_data.csv", sep=";")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If the file fails with a decoding error, try another encoding:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df = pd.read_csv("sales_data.csv", encoding="latin1")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If dates do not work with &lt;code&gt;.dt&lt;/code&gt;, parse them:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df["order_date"] = pd.to_datetime(df["order_date"], errors="coerce")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If numeric columns load as text, convert them:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df["unit_price"] = pd.to_numeric(df["unit_price"], errors="coerce")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you need to update values conditionally, use &lt;code&gt;.loc&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df.loc[df["category"].isna(), "category"] = "Unknown"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Avoid this chained-assignment pattern:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df\["category"\][df["category"].isna()] = "Unknown"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If your exported CSV contains an &lt;code&gt;Unnamed: 0&lt;/code&gt; column, export with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;df.to_csv("cleaned_sales_data.csv", index=False)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A good habit solves many of these mistakes, inspect before cleaning, and recheck after cleaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices Checklist
&lt;/h2&gt;

&lt;p&gt;Here is a best practice checklist I have compiled and encourage you to use this checklist when you analyze your own CSV files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the raw file unchanged.&lt;/li&gt;
&lt;li&gt;Inspect the dataset before cleaning it.&lt;/li&gt;
&lt;li&gt;Clean column names early.&lt;/li&gt;
&lt;li&gt;Parse dates before time-based analysis.&lt;/li&gt;
&lt;li&gt;Treat missing values based on column meaning.&lt;/li&gt;
&lt;li&gt;Check duplicates before removing them.&lt;/li&gt;
&lt;li&gt;Convert data types deliberately.&lt;/li&gt;
&lt;li&gt;Create calculated columns for the metrics you need.&lt;/li&gt;
&lt;li&gt;Write analysis around specific questions.&lt;/li&gt;
&lt;li&gt;Sort summary tables before interpreting them.&lt;/li&gt;
&lt;li&gt;Use charts only when they clarify a pattern.&lt;/li&gt;
&lt;li&gt;Save cleaned data separately.&lt;/li&gt;
&lt;li&gt;Export with &lt;code&gt;index=False&lt;/code&gt; unless the index matters.
## Practice Exercise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Extend the analysis with three questions. First, find revenue by payment method:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payment_sales = (
    df.groupby("payment_method", as_index=False)["total_sales"]
    .sum()
    .sort_values("total_sales", ascending=False)
)

print(payment_sales)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, find which country bought the most units:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;country_units = (
    df.groupby("customer_country", as_index=False)["quantity"]
    .sum()
    .sort_values("quantity", ascending=False)
)

print(country_units)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, calculate the share of revenue from Furniture:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;furniture_revenue = df.loc[
    df["category"] == "Furniture",
    "total_sales"
].sum()

furniture_share = furniture_revenue / df["total_sales"].sum()

print(f"Furniture revenue share: {furniture_share:.1%}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Export the payment summary:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payment_sales.to_csv("payment_sales.csv", index=False)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This exercise follows the same pattern as the main project: ask a question, group or filter the data, calculate the metric, sort the result, and save the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Learned Today
&lt;/h2&gt;

&lt;p&gt;You started with a raw ecommerce CSV and turned it into a practical sales analysis. You created &lt;code&gt;sales_data.csv&lt;/code&gt;, loaded it into Pandas, inspected its structure, cleaned column names, removed a duplicate row, handled missing dates, categories, and prices, and created a &lt;code&gt;total_sales&lt;/code&gt; metric.&lt;br&gt;
Then you used that cleaned data to answer business questions about total revenue, category performance, product performance, country revenue, monthly trends, and high-value orders. You also created two charts and exported cleaned and summarized CSV files.&lt;br&gt;
The final files are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cleaned_sales_data.csv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;category_sales.csv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;product_sales.csv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;country_sales.csv&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;monthly_sales.csv&lt;/code&gt;
## Conclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You analyzed a CSV file with Python and Pandas by following a repeatable workflow: load, inspect, clean, calculate, analyze, visualize, and export. Use the same structure with your own CSV files. Replace the sample file, update the column names, define your questions, and adapt the cleaning rules to your data. The specific dataset will change, but the habit stays the same: understand the file before you trust the numbers.&lt;/p&gt;

</description>
      <category>datascience</category>
      <category>python</category>
      <category>pandas</category>
      <category>analytics</category>
    </item>
    <item>
      <title>How to Integrate YouTube Video Player in a Flutter Application</title>
      <dc:creator>Gun</dc:creator>
      <pubDate>Tue, 12 Mar 2024 14:11:20 +0000</pubDate>
      <link>https://dev.to/gunkev/how-to-integrate-youtube-video-player-in-a-flutter-application-4hil</link>
      <guid>https://dev.to/gunkev/how-to-integrate-youtube-video-player-in-a-flutter-application-4hil</guid>
      <description>&lt;p&gt;YouTube is one of the world’s popular social media platforms for hosting and sharing videos. You can find a great list of music, movies, tutorials, documentaries and interviews in any domain there. Many enterprises, corporations and freelancers create and share videos on their various channels to enhance communication and provide a better user experience on the various services they offer. &lt;a href="http://Youtube.com" rel="noreferrer noopener"&gt;YouTube&lt;/a&gt; has become an indispensable social asset in the daily lives of many entrepreneurs and organizations.&lt;/p&gt;

&lt;h2 id="Objectives"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Objectives" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="Objectives"&gt;Objectives&lt;/h2&gt;

&lt;p&gt;This guide will cover the necessary steps you need to follow to integrate YouTube into your Flutter application. In a nutshell, you will build a simple application like in the demo below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://drive.google.com/drive/folders/1WNLfxjnQEFAv0rBrqvVVHFQNx9pP4ptF?usp=sharing" rel="noreferrer noopener"&gt;Youtube Flutter Player Demo in Drive&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="Common-Use-Cases-Where-You-Should-Add-YouTube-Integration-in-Your-Mobile-App"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Common-Use-Cases-Where-You-Should-Add-YouTube-Integration-in-Your-Mobile-App" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="Common-Use-Cases-Where-You-Should-Add-YouTube-Integration-in-Your-Mobile-App"&gt;Common Use Cases Where You Should Add YouTube Integration in Your Mobile App&lt;/h2&gt;

&lt;p&gt;Before diving into the main subject, it’s important to know when and where to integrate YouTube into your application. Some of the common use cases are:&lt;/p&gt;

&lt;h3 id="Integrating-Video-Tutorials"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Integrating-Video-Tutorials" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Integrating-Video-Tutorials"&gt;Integrating Video Tutorials&lt;/h3&gt;

&lt;p&gt;One of the easiest ways to enhance user experience and improve engagement is by adding video tutorials from your channel or various subject matters that can interest your audience.&lt;/p&gt;

&lt;p&gt;If you own a course or food application or a simple informational application, integrating YouTube can help users easily and efficiently get access to video content from your various channels that align with their interests.&lt;/p&gt;

&lt;h3 id="Integrating-in-Movies-Application"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Integrating-in-Movies-Application" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Integrating-in-Movies-Application"&gt;Integrating in Movies Application&lt;/h3&gt;

&lt;p&gt;Adding movie trials or reviews via YouTube in your movie application allows users to get a preview of a specific movie and helps them decide whether to watch it or not. You can equally add behind-the-scene footage to spice up the app and encourage users to spend longer time on your application hence improving user engagement.&lt;/p&gt;

&lt;h3 id="Informational-oriented-application"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Informational-oriented-application" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Informational-oriented-application"&gt;Informational oriented application&lt;/h3&gt;

&lt;p&gt;Informational-centered applications that provide articles, blog posts, and news about a variety of subjects can be enhanced by adding video-related content to improve user comprehension further.&lt;/p&gt;

&lt;h3 id="Monetization"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Monetization" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Monetization"&gt;Monetization&lt;/h3&gt;

&lt;p&gt;Implementing a YouTube player with ad support in your application provides a revenue stream for your application. For example, if you have a food recipe application, you can generate revenue by displaying YouTube videos with ads before, during or after the video.&lt;/p&gt;

&lt;h3 id="Live-Stream-Events"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Live-Stream-Events" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Live-Stream-Events"&gt;Live Stream Events&lt;/h3&gt;

&lt;p&gt;You can embed YouTube live stream video using the YouTube LIVE API into your application easily for real-time interaction with your audience, product launch or Q&amp;amp;A sessions which can improve the kind of services you offer and user engagement.&lt;/p&gt;

&lt;h3 id="Product-Demonstrations"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Product-Demonstrations" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Product-Demonstrations"&gt;Product Demonstrations&lt;/h3&gt;

&lt;p&gt;For educational applications or e-commerce applications, you can integrate YouTube videos to showcase the product features, the benefits of using it and real-life usage to the users in-app.&lt;/p&gt;

&lt;h3 id="Travel"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Travel" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Travel"&gt;Travel&lt;/h3&gt;

&lt;p&gt;A mobile app that includes travel guides or travel-related content with embedded YouTube videos showcasing popular destinations, attractions, or travel tips.&lt;/p&gt;

&lt;p&gt;For example, users can easily visualize popular spots instantly rather than navigating through a list of selected images.&lt;/p&gt;

&lt;h3 id="Fitness-and-Music"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Fitness-and-Music" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Fitness-and-Music"&gt;Fitness and Music&lt;/h3&gt;

&lt;p&gt;In fitness applications, you can incorporate YouTube videos that demonstrate and guide users on how to perform certain movements.&lt;/p&gt;

&lt;p&gt;Keep in mind that while integrating these YouTube features into your mobile application, it’s important to always ensure compliance with YouTube’s terms of service.&lt;/p&gt;

&lt;h2 id="Integrating-YouTube-in-a-Flutter-Application"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Integrating-YouTube-in-a-Flutter-Application" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="Integrating-YouTube-in-a-Flutter-Application"&gt;Integrating YouTube in a Flutter Application&lt;/h2&gt;

&lt;p&gt;In this section, we will work you through how to integrate a Youtube player into your &lt;a href="https://www.learn-dev-tools.blog/what-is-the-flutter-framework-a-beginners-introduction-to-flutter/" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt; application.&lt;/p&gt;

&lt;h3 id="Prerequisites"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Prerequisites" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Prerequisites"&gt;Prerequisites&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pub.dev/packages/youtube_player_flutter" rel="noreferrer noopener"&gt;YouTube Player Flutter&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;Basic Dart Knowledge&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="Creating-a-Flutter-Application"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Creating-a-Flutter-Application" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Creating-a-Flutter-Application"&gt;Creating a Flutter Application&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.learn-dev-tools.blog/how-to-install-flutter-on-windows-in-a-few-steps/" rel="noopener noreferrer"&gt;Install Flutter&lt;/a&gt;, open your Android Studio editor and create a new Flutter project or open your terminal and run the following command to get started with a Flutter project.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;flutter create example_project_youtube&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;example_project_youtube&lt;/code&gt; is the name of the project, and you can replace it with any name you want. Once the project is complete, choose an emulator or connect your phone and run the following command to launch it.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;flutter run&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="Adding-YouTube-Package"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Adding-YouTube-Package" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Adding-YouTube-Package"&gt;Adding YouTube Package&lt;/h3&gt;

&lt;p&gt;To add the &lt;code&gt;youTube_player_flutter&lt;/code&gt; package, open another terminal and run the following command:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;flutter pub add youtube_player_flutter&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;youtube_player_flutter&lt;/code&gt; plugin for playing or streaming inline YouTube videos using the official iFrame player API. This plugin supports both Android and iOS.&lt;/p&gt;

&lt;p&gt;This will add the &lt;code&gt;youtube_player_flutter&lt;/code&gt; package in the dependencies section of your &lt;code&gt;pubspec.yaml&lt;/code&gt; located at the root of your project.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  youtube_player_flutter: ^8.1.2&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now create two dart files in which one will contain the video JSON data model and the last will be the YouTube player page.&lt;/p&gt;

&lt;p&gt;We also have the &lt;code&gt;main.dart&lt;/code&gt; file which looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter_app_youtube/screens/YoutubeScreen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.red),
        useMaterial3: true,
      ),
      home: const YouTubeScreen(),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Create a &lt;code&gt;lib/model/VideoModel.dart&lt;/code&gt; and add the following code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import 'dart:convert';

class VideoModel {
  int? id;
  String? name;
  String? url;

  VideoModel(this.id, this.name, this.url);

  VideoModel.fromJson(Map&amp;lt;String, dynamic&amp;gt; json) {
    id = json["id"];
    name = json['name'].toString();
    url = json['url'];
  }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Create YouTubeVideoScreen.dart&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';

class YouTubeScreen extends StatefulWidget {

  const YouTubeScreen({Key? key}) : super(key: key);

  @override
  _YouTubeScreenState createState() =&amp;gt; _YouTubeScreenState();
}

class _YouTubeScreenState extends State&amp;lt;YouTubeScreen&amp;gt; {
  String? videoId;

  @override
  void initState() {
    super.initState();
    videoId = 't31fj-0t3Dw';
  }

  @override
  Widget build(BuildContext context) {
    return YoutubePlayer(
      controller: YoutubePlayerController(
        initialVideoId: videoId!,
        flags: const YoutubePlayerFlags(
          autoPlay: true, 
          mute: false
        )
      )
    );
  }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A Youtube video url is generally in the form:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://www.youtube.com/watch?v='Your video id here'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;v&lt;/code&gt; in the url contains the &lt;code&gt;id&lt;/code&gt; of your video. So put this id in the &lt;code&gt;videoId&lt;/code&gt; variable and test your application&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%2Fhackmd.io%2F_uploads%2FHkUCW6w56.jpg" 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%2Fhackmd.io%2F_uploads%2FHkUCW6w56.jpg" alt="integrate_youtube_vide" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="Incorporating-Features-and-Customizing-YouTube-Player"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Incorporating-Features-and-Customizing-YouTube-Player" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Incorporating-Features-and-Customizing-YouTube-Player"&gt;Incorporating Features and Customizing YouTube Player&lt;/h3&gt;

&lt;h3 id="Show-Video-Progression-Indicator"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Show-Video-Progression-Indicator" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Show-Video-Progression-Indicator"&gt;Show Video Progression Indicator&lt;/h3&gt;

&lt;p&gt;By default, the &lt;code&gt;showVideoProgressIndicator&lt;/code&gt; is set to true, you can set it to false if you don’t want to see the video progression indicator.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;showVideoProgressIndicator: false&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="Progress-Indicator-Color"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Progress-Indicator-Color" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Progress-Indicator-Color"&gt;Progress Indicator Color&lt;/h3&gt;

&lt;p&gt;Use the &lt;code&gt;progressIndicatorColor&lt;/code&gt; attribute to change the color of the video progress indicator. For example, below we have changed the color to green:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;progressIndicatorColor: Colors.green&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="Setting-FullScreen-Mode"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Setting-FullScreen-Mode" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Setting-FullScreen-Mode"&gt;Setting FullScreen Mode&lt;/h3&gt;

&lt;p&gt;To support Full screen you need to wrap the player in the &lt;code&gt;YoutubePlayerBuilder&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;YoutubePlayerBuilder(
  player: YoutubePlayer(
    controller: _controller,
  ),
  builder: (context, player){
    return Column(
      children: [
        player,
      ]
    );
  }
),&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="Adding-Players-Control"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Adding-Players-Control" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Adding-Players-Control"&gt;Adding Players Control&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;YoutubePlayerFlags&lt;/code&gt; allows you to define player flags. In the example below, we set the &lt;code&gt;autoplay&lt;/code&gt; to false, disabled the audio by setting &lt;code&gt;mute&lt;/code&gt; to true, enabled caption, and hide controls, looped the video and disabled video HD mode.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;YoutubePlayerFlags(
  autoPlay: false,
  mute: false,
  enableCaption: true,
  loop: true,
  forceHD: false,
  hideControls: true
)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you want to know more you can check the &lt;a href="https://pub.dev/documentation/youtube_player_flutter/latest/youtube_player_flutter/YoutubePlayerFlags-class.html" rel="noreferrer noopener"&gt;YoutublePlayerFlags Class&lt;/a&gt; and equally learn how to &lt;a href="https://pub.dev/packages/youtube_player_flutter#want-to-customize-the-player" rel="noreferrer noopener"&gt;customize other features&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="What’s-Next"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#What%E2%80%99s-Next" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="What’s-Next"&gt;What’s Next&lt;/h2&gt;

&lt;h3 id="Conclusion"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Conclusion" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Conclusion"&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;In this guide, we have learned how to integrate a YouTube video player into a Flutter application. By following the step-by-step guide and code examples provided, you can enhance your app's functionality and user experience by seamlessly incorporating YouTube videos. Remember to handle error cases gracefully and consider implementing additional features like Playback Speed and playlist integration to further enrich your app.&lt;/p&gt;

&lt;p&gt;Flutter YouTube Player increases the productivity of your applicant by providing visual and dynamic content that makes your application both informative and engaging. You can add a YouTube video player in any application as you can see through the use cases provided earlier.&lt;/p&gt;

&lt;p&gt;With the power of Flutter and YouTube integration, the possibilities are endless.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;br&gt;&lt;a href="https://github.com/Gunkev/youtube_player_flutter" rel="noreferrer noopener"&gt;Full GitHub Code of Tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id="Resources"&gt;&lt;a href="https://hackmd.io/OVg4JekzQImhG3ym6BP07A#Resources" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="Resources"&gt;Resources&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://pub.dev/packages/youtube_player_flutter" rel="noopener noreferrer"&gt;https://pub.dev/packages/youtube_player_flutter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/pulse/why-considering-video-integration-mobile-apps-profitable-abbas/" rel="noopener noreferrer"&gt;https://www.linkedin.com/pulse/why-considering-video-integration-mobile-apps-profitable-abbas/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>tutorial</category>
      <category>mobile</category>
      <category>flutter</category>
    </item>
    <item>
      <title>Opensea vs Blur: Which Platform Suits Your Needs?</title>
      <dc:creator>Gun</dc:creator>
      <pubDate>Tue, 12 Mar 2024 11:23:06 +0000</pubDate>
      <link>https://dev.to/gunkev/opensea-vs-blur-which-platform-suits-your-needs-1igj</link>
      <guid>https://dev.to/gunkev/opensea-vs-blur-which-platform-suits-your-needs-1igj</guid>
      <description>&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%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AkRb34JeuGEOgQueNX921iA.jpeg" 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%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AkRb34JeuGEOgQueNX921iA.jpeg" alt="" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the ever-evolving world of digital assets and non-fungible tokens (NFTs), choosing the right platform for buying, selling, or exploring NFTs can be a daunting task. Two of the leading platforms in this space are OpenSea and Blur. Each has its unique features, community, and approach to the NFT market. In this post, we’ll dive deep into the differences between OpenSea and Blur, helping you decide which platform best suits your needs as an NFT enthusiast, artist, or investor.&lt;/p&gt;

&lt;h3&gt;OpenSea: The Gateway to&amp;nbsp;NFTs&lt;/h3&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%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AsicmcqKUFNxiEpI6-H5iJQ.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%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AsicmcqKUFNxiEpI6-H5iJQ.png" alt="" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://opensea.io/" rel="noreferrer noopener"&gt;OpenSea&lt;/a&gt; has established itself as the go-to marketplace for &lt;a href="https://docs.alchemy.com/docs/nft-transactions" rel="noreferrer noopener"&gt;NFT transactions&lt;/a&gt;, often referred to as the &lt;a href="https://www.ebay.com/" rel="noreferrer noopener"&gt;eBay&lt;/a&gt; for digital assets. It’s designed to cater to a broad audience, from beginners in the NFT space to seasoned collectors and investors.&lt;/p&gt;

&lt;h3&gt;Key Features:&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Diverse Marketplace&lt;/strong&gt;: OpenSea boasts an expansive collection of NFTs across various categories, including art, domain names, virtual worlds, trading cards, and collectibles. This diversity makes it an attractive platform for users with varied interests.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;User-Friendly Interface&lt;/strong&gt;: The platform is known for its intuitive and easy-to-navigate interface, making it accessible for users of all levels of experience. Whether you’re creating, buying, or selling NFTs, the process is straightforward.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Cross-Blockchain Support&lt;/strong&gt;: OpenSea supports multiple blockchains, including Ethereum, Polygon, and Klaytn. This cross-chain functionality allows users to explore a wider range of NFTs and engage with different blockchain communities.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Community and Resources&lt;/strong&gt;: OpenSea offers extensive resources for new users, including guides, FAQs, and a supportive community. These resources are invaluable for those new to the NFT space.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Pros:&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Wide range of NFTs&lt;/li&gt;



&lt;li&gt;User-friendly for beginners&lt;/li&gt;



&lt;li&gt;Strong community support&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Cons:&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Transaction fees can be high on certain blockchains&lt;/li&gt;



&lt;li&gt;Some users have reported issues with customer service&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Blur: The Professional’s NFT Marketplace&lt;/h3&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%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2ABOngPgujtz0nA9eOWyxj8g.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%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2ABOngPgujtz0nA9eOWyxj8g.png" alt="" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blur.io/" rel="noreferrer noopener"&gt;Blur&lt;/a&gt; positions itself as a platform for professional NFT traders and artists. It’s known for its advanced features and analytics, catering to users looking for a more data-driven approach to the NFT market.&lt;/p&gt;

&lt;h3&gt;Key Features:&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Trading Tools&lt;/strong&gt;: Blur offers sophisticated tools and analytics for users to track the market, analyze trends, and make informed decisions. This is particularly beneficial for professional traders and investors.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Batch Buying and Selling&lt;/strong&gt;: The platform allows users to buy or sell multiple NFTs in a single transaction, streamlining the process and saving on transaction fees.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Rewards System&lt;/strong&gt;: Blur incentivizes platform activity through a rewards system, offering BLUR tokens for actions like listing NFTs and participating in auctions. This can be particularly attractive for active traders and sellers.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Focus on Speed and Efficiency&lt;/strong&gt;: The platform is optimized for speed, aiming to provide a seamless and efficient experience for high-frequency traders.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Pros:&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Advanced tools and analytics&lt;/li&gt;



&lt;li&gt;Incentivized participation&lt;/li&gt;



&lt;li&gt;Efficient batch transactions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Cons:&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Might be overwhelming for beginners&lt;/li&gt;



&lt;li&gt;More focused on trading than the community aspect&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Comparing OpenSea and&amp;nbsp;Blur&lt;/h3&gt;

&lt;p&gt;When deciding between OpenSea and Blur, consider your objectives and preferences in the NFT space.&lt;/p&gt;

&lt;h3&gt;For Beginners and Enthusiasts:&lt;/h3&gt;

&lt;p&gt;If you’re new to NFTs or looking for a broad marketplace with diverse offerings, OpenSea is likely the better choice. Its user-friendly interface, extensive resources, and supportive community make it an ideal starting point for exploring the world of NFTs.&lt;/p&gt;

&lt;h3&gt;For Artists:&lt;/h3&gt;

&lt;p&gt;Artists looking to showcase their work might find OpenSea more accommodating due to its larger audience and the diversity of its user base. However, Blur’s advanced tools and analytics could also appeal to artists interested in market trends and the trading aspect of their work.&lt;/p&gt;

&lt;h3&gt;For Professional Traders and Investors:&lt;/h3&gt;

&lt;p&gt;For those with experience in the NFT market, particularly traders and investors seeking advanced tools and analytics, Blur stands out. Its focus on speed, efficiency, and the rewards system makes it a compelling option for users looking to maximize their engagement and returns.&lt;/p&gt;

&lt;h3&gt;Community vs. Trading&amp;nbsp;Focus:&lt;/h3&gt;

&lt;p&gt;OpenSea fosters a broader community of NFT enthusiasts, making it a more suitable platform for users interested in the social and discovery aspects of NFTs. On the other hand, Blur appeals to users with a strong focus on the trading and investment side, offering tools and features that cater to this demographic.&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;Choosing between OpenSea and Blur ultimately depends on your specific needs, level of expertise, and interests within the NFT space. OpenSea offers a welcoming environment for newcomers and a diverse marketplace for collectors and artists, emphasizing community and accessibility. Blur, meanwhile, caters to the more professional segment of the market, providing advanced tools and analytics for serious traders and investors.&lt;/p&gt;

&lt;p&gt;Before making your decision, consider what you value most in an NFT platform — whether it’s the community, diversity of assets, trading&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Build an Electronic Commerce Store with Medusajs</title>
      <dc:creator>Gun</dc:creator>
      <pubDate>Wed, 25 Oct 2023 12:55:57 +0000</pubDate>
      <link>https://dev.to/gunkev/how-to-build-an-electronic-commerce-store-with-medusajs-4p5i</link>
      <guid>https://dev.to/gunkev/how-to-build-an-electronic-commerce-store-with-medusajs-4p5i</guid>
      <description>&lt;p&gt;The demand and supply of goods and services keep evolving daily. Businesses need to keep an eye on these evolutions to adopt the right ecommerce platform to distribute their goods and services to various end users. &lt;/p&gt;

&lt;p&gt;Headless commerce is becoming increasingly popular because of its adaptability and scalability. A great option for constructing a headless commerce store is Medusa, an open source and scalable ecommerce engine. Next.js offers a robust framework for building storefronts. &lt;/p&gt;

&lt;p&gt;With the help of Medusa and Next.js, you can develop a powerful and scalable online store that is tailored to the particular requirements of your company or business. Regardless of your experience as a developer or online retailer, this tutorial will guide you on building and customizing an electronic store. &lt;/p&gt;

&lt;p&gt;Here’s a demo of the application with Medusa and Next.js by following this tutorial:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://prod-files-secure.s3.us-west-2.amazonaws.com/e27708d9-808e-4b35-b986-9f26951398de/5060b0c4-c5a5-4c04-a058-74cf16653819/demo_medusa_electronic_store.mp4" rel="noopener noreferrer"&gt;demo medusa electronic store.mp4&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Will You Learn?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Set up a Storefront with Medusa’s &lt;strong&gt;&lt;a href="https://docs.medusajs.com/starters/nextjs-medusa-starter" rel="noopener noreferrer"&gt;Next.js default starter template&lt;/a&gt;.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Add product using the admin dashboard.&lt;/li&gt;
&lt;li&gt;Display products in your storefront.&lt;/li&gt;
&lt;li&gt;Customize your storefront.&lt;/li&gt;
&lt;li&gt;Add a Search option to your store.&lt;/li&gt;
&lt;li&gt;Add a Payment option to your store.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the code for this tutorial in this &lt;a href="https://github.com/Gunkev/electronic-storefront-medusa" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Next.js?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; is an open-source React framework for building user-friendly web applications. &lt;a href="https://beta.reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt; is a JavaScript library for building interactive user interfaces. Next.js enables server-side rendering and requires minimal or no configuration.&lt;/p&gt;

&lt;p&gt;Medusa offers two storefront starters: Next.js and Gatsby. It's easy to install them with the CLI command. Medusa Next.js storefront has many ecommerce pre-build features like a shopping cart, product display, payment processing, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Medusa?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://medusajs.com/" rel="noopener noreferrer"&gt;Medusa&lt;/a&gt; is an &lt;a href="https://medusajs.com/blog/composable-ecommerce-platform/" rel="noopener noreferrer"&gt;open-source digital commerce infrastructure&lt;/a&gt; built with Node.js and provides many ecommerce features such as RMA flows, product and collection management, order management, customization, and more. &lt;/p&gt;

&lt;p&gt;These are some features that set Medusa apart from other ecommerce platforms. Medusa also offers a great developer experience, allowing developers to create amazing and scalable stores with minimal effort.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medusajs.com/blog/composable-commerce/" rel="noopener noreferrer"&gt;Medusa's headless architecture&lt;/a&gt; enables you to build your store with the framework or language of your choice. There are no constraints, as you can use &lt;a href="https://docs.medusajs.com/api/admin" rel="noopener noreferrer"&gt;Medusa's API&lt;/a&gt; to connect the &lt;a href="https://docs.medusajs.com/admin/quickstart" rel="noopener noreferrer"&gt;Medusa admin&lt;/a&gt; to the storefront to perform actions like the product, customer, order, payment management, and many more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating your Electronic Commerce Platform
&lt;/h2&gt;

&lt;p&gt;This section highlights the steps to build your electronic store with Next.js and Medusa.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before you begin, make sure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en" rel="noopener noreferrer"&gt;Nodejs&lt;/a&gt; version 14 or later&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;Git&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://yarnpkg.com/" rel="noopener noreferrer"&gt;Yarn&lt;/a&gt; or Npm(This tutorial uses Yarn)&lt;/li&gt;
&lt;li&gt;A code editor like &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VSCode&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://www.algolia.com/" rel="noopener noreferrer"&gt;Algolia&lt;/a&gt; account&lt;/li&gt;
&lt;li&gt;A &lt;a href="http://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt; account&lt;/li&gt;
&lt;li&gt;S3 for file upload service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will find all the resources used in this tutorial, such as &lt;a href="https://github.com/Gunkev/electronic-storefront-medusa/tree/main/public/store%20images" rel="noopener noreferrer"&gt;icons and images in the Git repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Set Up the Medusa Server for the Electronic Store
&lt;/h3&gt;

&lt;p&gt;If you have everything installed, follow these steps to set up your &lt;a href="https://github.com/medusajs/medusa" rel="noopener noreferrer"&gt;Medusa&lt;/a&gt; project.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the Medusa CLI tool with the following command:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn global add @medusajs/medusa-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create your Medusa application with this command:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn create medusa-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upon executing, this command will ask you where you which to install your project. Enter a name there; in this tutorial, it is &lt;code&gt;ElectronicStore&lt;/code&gt;. Then, press enter. For this tutorial, choose the &lt;code&gt;Medusa-starter-default&lt;/code&gt; for the backend and the &lt;code&gt;Next.js starter&lt;/code&gt; template for the storefront. &lt;/p&gt;

&lt;p&gt;Normally you should have the same information as the screenshot below.&lt;/p&gt;

&lt;p&gt;![How to Build an Electronic Commerce Store with Medusa and Nextjs]&lt;a href="https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/starter-1024x302.png" rel="noopener noreferrer"&gt;https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/starter-1024x302.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Upon the execution of this command, Medusa will create three folders in the &lt;code&gt;ElectronicStore&lt;/code&gt;: storefront, admin, and backend.&lt;/p&gt;

&lt;p&gt;To start each part of the application, open the three command line/terminal tabs and run the following command with respect to each folder:&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="c"&gt;# Medusa server&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;ElectronStore/backend
yarn start

&lt;span class="c"&gt;# Admin&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;ElectronStore/admin
yarn start

&lt;span class="c"&gt;# Storefront&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;ElectronStore/storefront
yarn dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After executing those commands, visit &lt;code&gt;localhost:8000&lt;/code&gt; to view your storefront.&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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe27708d9-808e-4b35-b986-9f26951398de%2F39ee85f8-0c84-42c8-8d2f-e600fd4dfb6a%2Fstoref.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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe27708d9-808e-4b35-b986-9f26951398de%2F39ee85f8-0c84-42c8-8d2f-e600fd4dfb6a%2Fstoref.png" alt="storef.PNG" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, visit your admin dashboard by opening your browser on &lt;code&gt;localhost:7000&lt;/code&gt;. 7000 is the default admin port.&lt;/p&gt;

&lt;p&gt;!&lt;a href="https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/admin-1024x552.png" rel="noopener noreferrer"&gt;https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/admin-1024x552.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next step consists of styling your storefront. This tutorial uses &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt; very useful for building modern websites. Note that you can equally set up your &lt;a href="https://docs.medusajs.com/admin/quickstart" rel="noopener noreferrer"&gt;Medusa admin&lt;/a&gt;, &lt;a href="https://docs.medusajs.com/starters/nextjs-medusa-starter" rel="noopener noreferrer"&gt;Medusa storefront&lt;/a&gt;, and &lt;a href="https://docs.medusajs.com/development/backend/install" rel="noopener noreferrer"&gt;Medusa Backend&lt;/a&gt; separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Styling the Electronic Commerce Storefront
&lt;/h2&gt;

&lt;p&gt;This section shows how to style your storefront using &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;. With &lt;code&gt;create-medusa-app&lt;/code&gt;, the Next.js starter template already contains Tailwind. However, you need to configure the &lt;code&gt;tailwind.config.js&lt;/code&gt; as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @type {import('tailwindcss').Config} */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./app/**/*.{js,ts,jsx,tsx}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./pages/**/*.{js,ts,jsx,tsx}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/**/*.{js,ts,jsx,tsx}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Or if using `src` directory:&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/**/*.{js,ts,jsx,tsx}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;extend&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="na"&gt;plugins&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that you have successfully added Tailwind to your project, you must customize your storefront.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customizing the Storefront
&lt;/h3&gt;

&lt;p&gt;Start by changing the style of the header &lt;code&gt;storefront/src/module/layout/templates/nav/index.tsx&lt;/code&gt; with the code below:&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;const&lt;/span&gt; &lt;span class="nx"&gt;Nav&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="p"&gt;}&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isHome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsHome&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;setIsHome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;setIsHome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;pathname&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toggle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMobileMenu&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
      &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;clsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sticky top-0 inset-x-0 z-50 group&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;!fixed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isHome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&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;header&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"relative h-16 px-8 mx-auto transition-colors bg-white border-b border-transparent duration-200 group-hover:bg-white group-hover:border-gray-200 border-b-[#f1f1f1]"&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;nav&lt;/span&gt;
          &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;clsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-gray-900 flex items-center justify-between w-full h-full text-small-regular transition-colors duration-200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex-1 basis-0 h-full flex items-center"&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"block small:hidden"&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;Hamburger&lt;/span&gt; &lt;span class="na"&gt;setOpen&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toggle&lt;/span&gt;&lt;span class="si"&gt;}&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"hidden small:block h-full"&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;DropdownMenu&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;&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="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center h-full"&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;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&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;a&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-xl-semi uppercase"&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;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/logo.png"&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="si"&gt;}&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;a&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;Link&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center gap-x-6 h-full flex-1 basis-0 justify-end"&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"hidden small:flex items-center gap-x-6 h-full"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="si"&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;FEATURE_SEARCH_ENABLED&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DesktopSearchModal&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/account"&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;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Account&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&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;Link&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CartDropdown&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;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;nav&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;MobileMenu&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;header&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="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;Nav&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This code above changes the navbar by adding a border and logo.&lt;/p&gt;

&lt;p&gt;To change the banner image, locate your &lt;code&gt;storefront/src/modules/home/components/hero/index.tsx&lt;/code&gt;, which is the hero section and replace it with the code below:&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;const&lt;/span&gt; &lt;span class="nx"&gt;Hero&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="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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"h-[90vh] w-full relative"&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-white absolute inset-0 z-10 flex flex-col justify-center items-center text-center small:text-left small:justify-end small:items-start small:p-32"&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;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-2xl-semi mb-4 drop-shadow-md shadow-black"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Hi 👋 welcome to my electronic shop
        &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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-base-regular max-w-[32rem] mb-6 drop-shadow-md shadow-black"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          The best electronic devices to help you in your daily tasks both in
          the professional and personal life.
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&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;UnderlineLink&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/store"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Explore products&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;UnderlineLink&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-white h-[90vh] w-full"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        The best electronic devices to help you in your daily tasks both
        in the professional and personal life.
      &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;Image&lt;/span&gt;
        &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/hero2.jpg"&lt;/span&gt;
        &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"fill"&lt;/span&gt;
        &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"eager"&lt;/span&gt;
        &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;objectFit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"cover"&lt;/span&gt;
        &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"computer"&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"absolute inset-0"&lt;/span&gt;
        &lt;span class="na"&gt;draggable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"false"&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-black h-[90vh] w-full absolute top-0 opacity-50"&lt;/span&gt;&lt;span class="p"&gt;&amp;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="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="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;Hero&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code block adds a banner image and text to the hero section. &lt;/p&gt;

&lt;p&gt;Next, change how you display feature products in the home section. &lt;code&gt;storefront/src/module/home/components/featured-products/index.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;const&lt;/span&gt; &lt;span class="nx"&gt;FeaturedProducts&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="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="p"&gt;...&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flex flex-col items-center text-center mb-16&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-base-regular text-gray-600 mb-6"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Latest products
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&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;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-2xl-regular text-gray-900 max-w-lg mb-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Our newest electronic gadgets to make your life smooth.
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&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;UnderlineLink&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/store"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Explore products&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;UnderlineLink&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="err"&gt;/&lt;/span&gt;&lt;span class="na"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
       ...
  )
}

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

&lt;/div&gt;



&lt;p&gt;The code above changes the text of feature products of your store.&lt;/p&gt;

&lt;p&gt;Change the section just before the footer  &lt;code&gt;storefront/src/modules/layout/components/footer-cta/index.tsx&lt;/code&gt; by adding the following code:&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;const&lt;/span&gt; &lt;span class="nx"&gt;FooterCTA&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="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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-gray-100 w-full"&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"content-container flex flex-col-reverse gap-y-8 small:flex-row small:items-center justify-between py-16 relative"&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-2xl-semi"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Check out the lastest gadgets&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mt-6"&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;UnderlineLink&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/store"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Explore products&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;UnderlineLink&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;&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="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"relative w-full aspect-square small:w-[35%] small:aspect-[28/36]"&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;Image&lt;/span&gt;
            &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/hero3.jpg"&lt;/span&gt;
            &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
            &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"fill"&lt;/span&gt;
            &lt;span class="na"&gt;objectFit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"cover"&lt;/span&gt;
            &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"absolute inset-0"&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;&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="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="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;FooterCTA&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you are changing the default text as well as the default image. &lt;/p&gt;

&lt;p&gt;Now move to the footer file &lt;code&gt;storefront/src/modules/layout/components/footer-nav/index.tsx&lt;/code&gt; and replace it with the following code:&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;const&lt;/span&gt; &lt;span class="nx"&gt;FooterNav&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;collections&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCollections&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"content-container flex flex-col gap-y-8 pt-16 pb-8"&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col gap-y-6 xsmall:flex-row items-start justify-between"&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&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;a&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-xl-semi uppercase"&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;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/logo.png"&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="si"&gt;}&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;a&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;Link&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;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"py-5 text-sm text-[#999]"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Get the best electronics from the best place
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col-reverse gap-y-0 justify-center xsmall:items-center xsmall:flex-row xsmall:justify-between"&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;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-xsmall-regular text-gray-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              © Copyright 2023 ES store
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"min-w-[316px] flex xsmall:justify-end"&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;CountrySelect&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;&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="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="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-small-regular grid grid-cols-3 gap-x-16"&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col gap-y-2"&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;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-base-semi"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Collections&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&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;ul&lt;/span&gt;
              &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;clsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid grid-cols-1 gap-y-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid-cols-2&lt;/span&gt;&lt;span class="dl"&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;collections&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&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;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`/collections/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&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;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&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;Link&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;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&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;&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="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="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="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;FooterNav&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code snippet above adds new links and a logo in the footer. In the last part of the footer, change the background colour &lt;code&gt;storefront/src/modules/layout/components/medusa-cta/index.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;const&lt;/span&gt; &lt;span class="nx"&gt;MedusaCTA&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="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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"py-4 flex justify-center items-center w-full bg-[#333]"&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="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"content-container flex justify-center flex-1"&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;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://www.medusajs.com"&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"noreferrer"&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;PoweredBy&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;a&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;&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Head to &lt;code&gt;storefront/src/pages/index.tsx&lt;/code&gt; to modify the meta information of the home page:&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;const&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextPageWithLayout&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="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;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Home"&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Get the best electroics for your home and office from the best stores online with secure payment methods."&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;Hero&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;FeaturedProducts&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&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;By the end, you should have a store similar to the screenshot below: &lt;/p&gt;

&lt;p&gt;!&lt;a href="https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/store1-1024x470.png" rel="noopener noreferrer"&gt;https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/store1-1024x470.png&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Products in the Electronic Commerce Store
&lt;/h2&gt;

&lt;p&gt;By default, Medusa comes with some products on the storefront. You need to add your products. Medusa commonly uses a &lt;a href="https://docs.medusajs.com/development/file-service/overview" rel="noopener noreferrer"&gt;file service&lt;/a&gt; plugin to add products to the admin dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing and Integrating a File Service Plugin (S3)
&lt;/h3&gt;

&lt;p&gt;Simple Storage Service (S3) is a powerful open-source object storage suite available on any public and private cloud. Medusa uses file service plugins to host files such as images.&lt;/p&gt;

&lt;p&gt;This tutorial will use &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html" rel="noopener noreferrer"&gt;AWS S3&lt;/a&gt;, but you can use other file services like &lt;a href="https://docs.medusajs.com/plugins/file-service/spaces" rel="noopener noreferrer"&gt;Space&lt;/a&gt; and &lt;a href="https://docs.medusajs.com/plugins/file-service/minio" rel="noopener noreferrer"&gt;Minio&lt;/a&gt;. You need to have an &lt;a href="https://console.aws.amazon.com/console/home?nc2=h_ct&amp;amp;src=header-signin" rel="noopener noreferrer"&gt;AWS account&lt;/a&gt; and &lt;a href="https://docs.medusajs.com/plugins/file-service/s3" rel="noopener noreferrer"&gt;create a bucket&lt;/a&gt; if you have none. Equally, you need to &lt;a href="https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys" rel="noopener noreferrer"&gt;create an access and secret key&lt;/a&gt; that you will use in the next section to build your store.&lt;/p&gt;

&lt;p&gt;When you are done with that, install the S3 plugin in the backend with the command below:&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="nx"&gt;yarn&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="nx"&gt;medusa&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, open your &lt;code&gt;backend/.env&lt;/code&gt; file and add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;S3_URL=&amp;lt;YOUR_BUCKET_URL&amp;gt;
S3_BUCKET=&amp;lt;YOUR_BUCKET_NAME&amp;gt;
S3_REGION=&amp;lt;YOUR_BUCKET_REGION&amp;gt;
S3_ACCESS_KEY_ID=&amp;lt;YOUR_ACCESS_KEY_ID&amp;gt;
S3_SECRET_ACCESS_KEY=&amp;lt;YOUR_SECRET_ACCESS_KEY&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;YOUR_BUCKET_URL&amp;gt;&lt;/code&gt; corresponds to the URL of your bucket it's generally in the form: &lt;code&gt;http://&amp;lt;YOUR_BUCKET_NAME&amp;gt;.s3-website-&amp;lt;YOUR_BUCKET_REGION&amp;gt;.amazonaws.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;YOUR_BUCKET_NAME&amp;gt;&lt;/code&gt;: The name of your S3 bucket&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;YOUR_ACCESS_KEY_ID&lt;/code&gt;: The access key which was created earlier&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;YOUR_SECRET_ACCESS_KEY&amp;gt;&lt;/code&gt;: The secret key created earlier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To complete the S3 integration, add the following configuration to the array of plugins in the &lt;code&gt;backend/medusa-config.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`medusa-file-s3`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;s3_url&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;S3_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bucket&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;S3_BUCKET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;region&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;S3_REGION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;access_key_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;S3_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;secret_access_key&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;S3_SECRET_ACCESS_KEY&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, edit &lt;code&gt;storefront/next.config.js&lt;/code&gt; file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;domains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;YOUR_BUCKET_NAME&amp;gt;.s3.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;"&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;Add the domain from where you are uploading your images.  corresponds to the name of your bucket. Once you have finished setting up S3, you can add electronic products to your store.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Products with Medusa Admin
&lt;/h3&gt;

&lt;p&gt;To add products to your store, head to the admin dashboard (&lt;code&gt;localhost:7000&lt;/code&gt;). The default login credentials are &lt;code&gt;admin@medusa-test.com&lt;/code&gt; and &lt;code&gt;supersecret&lt;/code&gt;. The Medusa admin dashboard connects the Medusa server to help you to manage your store.&lt;/p&gt;

&lt;p&gt;Once you logged in, you should have a similar interface as below. Here you can manage your &lt;a href="https://docs.medusajs.com/user-guide/products/" rel="noopener noreferrer"&gt;products&lt;/a&gt;, &lt;a href="https://docs.medusajs.com/user-guide/orders/" rel="noopener noreferrer"&gt;orders&lt;/a&gt;, &lt;a href="https://docs.medusajs.com/user-guide/customers/manage" rel="noopener noreferrer"&gt;customer&lt;/a&gt; settings, and more.&lt;/p&gt;

&lt;p&gt;!&lt;a href="https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/dash-1024x464.png" rel="noopener noreferrer"&gt;https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/dash-1024x464.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Head to the product page and delete all the default products by clicking on the three dots at the end and selecting &lt;code&gt;delete&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;!&lt;a href="https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/deleteprod-1024x176.png" rel="noopener noreferrer"&gt;https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/deleteprod-1024x176.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After removing all the products, click on &lt;code&gt;add new product&lt;/code&gt; button to add some electronic products to the ecommerce store.&lt;/p&gt;

&lt;p&gt;!&lt;a href="https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/store3-1024x464.png" rel="noopener noreferrer"&gt;https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/store3-1024x464.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you go to &lt;code&gt;localhost:8000/store&lt;/code&gt; you will see your newly-added products.&lt;/p&gt;

&lt;p&gt;!&lt;a href="https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/store2-1024x469.png" rel="noopener noreferrer"&gt;https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/store2-1024x469.png&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration
&lt;/h2&gt;

&lt;p&gt;Now is the time to integrate the two main services: Search and Payment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Algolia Integration
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.algolia.com/" rel="noopener noreferrer"&gt;Algolia&lt;/a&gt; is an open-source UI search JavaScript library. It allows you to add advanced search functionalities to your store. Adding this search functionality to your store will greatly improve the user experience and &lt;a href="https://medusajs.com/blog/importance-of-optimizing-internal-search-in-ecommerce-websites/" rel="noopener noreferrer"&gt;optimize internal search&lt;/a&gt; in the electronic commerce store.&lt;/p&gt;

&lt;p&gt;To add Algolia to your application, you need to possess an &lt;a href="https://www.algolia.com/users/sign_up" rel="noopener noreferrer"&gt;Algolia account&lt;/a&gt;. Upon creating your account, follow &lt;a href="https://docs.medusajs.com/plugins/search/algolia#retrieve-api-keys" rel="noopener noreferrer"&gt;this documentation&lt;/a&gt; to create an &lt;a href="https://docs.medusajs.com/plugins/search/algolia#create-an-algolia-app" rel="noopener noreferrer"&gt;Algolia application&lt;/a&gt; and then, get your Algolia Application ID, Admin API Key, and Search-Only API Key.&lt;/p&gt;

&lt;p&gt;Next, install the Algolia plugin in your store with 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;yarn add medusa-plugin-algolia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add this environment variable to your Medusa backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALGOLIA_APP_ID=&amp;lt;YOUR_APP_ID&amp;gt;
ALGOLIA_ADMIN_API_KEY=&amp;lt;YOUR_ADMIN_API_KEY&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;&amp;lt;YOUR_APP_ID&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;YOUR_ADMIN_API_KEY&amp;gt;&lt;/code&gt; correspond to the Application ID and Admin API Key mentioned earlier. You can find them on the &lt;a href="https://www.algolia.com/account/api-keys/" rel="noopener noreferrer"&gt;API Keys page&lt;/a&gt; under your Algolia account.&lt;/p&gt;

&lt;p&gt;Add the following item into the &lt;code&gt;plugins&lt;/code&gt; array in &lt;code&gt;medusa-config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`medusa-plugin-algolia`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;application_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;ALGOLIA_APP_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;admin_api_key&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;ALGOLIA_ADMIN_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;searchableAttributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;attributesToRetrieve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;handle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thumbnail&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;variants&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;variant_sku&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;options&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;collection_title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;collection_handle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;images&lt;/span&gt;&lt;span class="dl"&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;searchableAttributes&lt;/code&gt; are attributes in a product that are searchable while &lt;code&gt;attributesToRetrieve&lt;/code&gt; are attributes to retrieve for each product result.&lt;/p&gt;

&lt;p&gt;Now, add the search UI to the storefront so that users can effectively perform product research. Fortunately, Next.js storefront Algolia integration is available out-of-the-box. To get everything ready, make sure the search feature is set to &lt;code&gt;true&lt;/code&gt; in the &lt;code&gt;store.config.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="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;"features"&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;"search"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the environmental variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEXT_PUBLIC_SEARCH_APP_ID=&amp;lt;YOUR_APP_ID&amp;gt;
NEXT_PUBLIC_SEARCH_API_KEY=&amp;lt;YOUR_SEARCH_API_KEY&amp;gt;
NEXT_PUBLIC_SEARCH_INDEX_NAME=products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;YOUR_APP_ID&amp;gt;&lt;/code&gt; corresponds to the Application ID.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;YOUR_SEARCH_API_KEY&amp;gt;&lt;/code&gt; corresponds to the Search-Only API Key. You can find this information in your Algolia &lt;a href="https://docs.medusajs.com/plugins/search/algolia#retrieve-api-keys" rel="noopener noreferrer"&gt;API Keys section&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To complete this integration, change the code in &lt;code&gt;src/lib/search-client.ts&lt;/code&gt; to the following:&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;algoliasearch&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;algoliasearch/lite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appId&lt;/span&gt; &lt;span class="o"&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;NEXT_PUBLIC_SEARCH_APP_ID&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&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;NEXT_PUBLIC_SEARCH_API_KEY&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test_key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;algoliasearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SEARCH_INDEX_NAME&lt;/span&gt; &lt;span class="o"&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;NEXT_PUBLIC_INDEX_NAME&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run your storefront and backend, you will notice that the search feature is available.&lt;/p&gt;

&lt;p&gt;!&lt;a href="https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/search-1024x553.png" rel="noopener noreferrer"&gt;https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/search-1024x553.png&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Payment Integration with&lt;/strong&gt; Stripe
&lt;/h3&gt;

&lt;p&gt;This section will guide you through how to set up Stripe payment methods in your Medusa backend, admin, and storefront. &lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt; is one of the simplest ways to accept online payments. It gives you the technical components needed to manage transactions safely. Other payment methods you can use with Medua are &lt;a href="https://medusajs.com/blog/how-we-won-medusa-hackathon-paystack-plugin/" rel="noopener noreferrer"&gt;Paystack&lt;/a&gt;, &lt;a href="https://docs.medusajs.com/plugins/payment/klarna" rel="noopener noreferrer"&gt;Klarna&lt;/a&gt;, and &lt;a href="https://docs.medusajs.com/plugins/payment/paypal" rel="noopener noreferrer"&gt;Paypal&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;To add Stripe to your store, you must possess a &lt;a href="https://dashboard.stripe.com/register" rel="noopener noreferrer"&gt;Stripe account&lt;/a&gt;. Like Algolia, you must &lt;a href="https://docs.medusajs.com/plugins/payment/stripe#retrieve-stripes-keys" rel="noopener noreferrer"&gt;retrieve your account's API Keys and secrets&lt;/a&gt; to connect Medusa to your Stripe account.&lt;/p&gt;

&lt;p&gt;In the root directory of the Medusa backend, use the following command to install the Stripe plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add medusa-payment-stripe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, you need to add configurations to your stripe plugin. In &lt;code&gt;medusa-config.js&lt;/code&gt;, add the following at the end of the &lt;code&gt;plugins&lt;/code&gt; array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plugins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`medusa-payment-stripe`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;api_key&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;STRIPE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;webhook_secret&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;STRIPE_WEBHOOK_SECRET&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Head to the dashboard of your Stripe account to retrieve the API Key and secret key. Then, in your Medusa backend, create &lt;code&gt;.env&lt;/code&gt; if it does not exist and add the Stripe key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STRIPE_API_KEY=sk_...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add Stripe’s webhook in the &lt;code&gt;.env&lt;/code&gt; file. You can get it by heading to the Stripe dashboard and clicking Add an Endpoint. Medusa backend's endpoint, Stripe webhook, is &lt;code&gt;{BACKEND_URL}/stripe/hooks&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Add this endpoint to its field if you are in production, and replace {BACKEND_URL} with the URL of your backend. Add a description, select at least one action, and then click Add Endpoint. Once the Webhook is created, you’ll see "Signing secret" in the Webhook details. Reveals the secret key by clicking &lt;code&gt;reveal&lt;/code&gt; it and copying it into your Medusa backend.&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STRIPE_WEBHOOK_SECRET=whsec_...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last part of Stripe integration consists of setting it up in the Medusa admin by adding a payment provider.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Settings → &lt;a href="https://docs.medusajs.com/user-guide/regions/manage" rel="noopener noreferrer"&gt;Regions&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Select a region to edit.&lt;/li&gt;
&lt;li&gt;Click on the icon at the top right of the first section on the right.&lt;/li&gt;
&lt;li&gt;Click on Edit Region Details from the dropdown.&lt;/li&gt;
&lt;li&gt;Under the provider's section, select the payment providers you want to add to the region. Here it's Stripe.&lt;/li&gt;
&lt;li&gt;Unselect the payment providers you want to remove from the region.&lt;/li&gt;
&lt;li&gt;Click Save.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, in your storefront, add the following environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEXT_PUBLIC_STRIPE_KEY=&amp;lt;YOUR_PUBLISHABLE_KEY&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;&amp;lt;YOUR_PUBLISHABLE_KEY&amp;gt;&lt;/code&gt; corresponds to your Stripe Publishable Key.&lt;/p&gt;

&lt;p&gt;If you run your Medusa storefront and backend, add products to your cart, and then proceed to checkout, you can now use Stripe as a payment method. Here is what the final result looks like: &lt;/p&gt;

&lt;p&gt;!&lt;a href="https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/store4-1024x558.png" rel="noopener noreferrer"&gt;https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/store4-1024x558.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You could also check out this &lt;a href="https://medusajs.com/blog/stripe-ecommerce/" rel="noopener noreferrer"&gt;Stripe guide&lt;/a&gt; for more information regarding its integration&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Store
&lt;/h2&gt;

&lt;p&gt;The final step of this tutorial consists of testing the whole store. While the Medusa server is still running, restart your storefront.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;code&gt;localhost:8000&lt;/code&gt; to see the final electronic commerce store: &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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe27708d9-808e-4b35-b986-9f26951398de%2F57ce858b-cdcc-4881-ba6c-695d34345578%2Fstore5.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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe27708d9-808e-4b35-b986-9f26951398de%2F57ce858b-cdcc-4881-ba6c-695d34345578%2Fstore5.png" alt="store5.PNG" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the store item in the navigation bar to open the store:&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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe27708d9-808e-4b35-b986-9f26951398de%2F86216436-9ea9-48ee-902c-34018524ce26%2Fstore7.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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe27708d9-808e-4b35-b986-9f26951398de%2F86216436-9ea9-48ee-902c-34018524ce26%2Fstore7.png" alt="store7.PNG" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add some products to your cart:&lt;/p&gt;

&lt;p&gt;!&lt;a href="https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/store8-1024x467.png" rel="noopener noreferrer"&gt;https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/store8-1024x467.png&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Checkout:&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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe27708d9-808e-4b35-b986-9f26951398de%2F34252a44-9c70-4bdb-a9e9-31a3f04b319c%2Fstore9.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%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fe27708d9-808e-4b35-b986-9f26951398de%2F34252a44-9c70-4bdb-a9e9-31a3f04b319c%2Fstore9.png" alt="store9.PNG" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;View your order:&lt;/p&gt;

&lt;p&gt;!&lt;a href="https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/store10-1024x465.png" rel="noopener noreferrer"&gt;https://www.learn-dev-tools.blog/wp-content/uploads/2023/03/store10-1024x465.png&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this tutorial, you learned how to build a headless electronic commerce platform with the Medusa engine, using the Next.js Storefront and integrating services like search and payment using Algolia and Stripe.&lt;/p&gt;

&lt;p&gt;However, you can add many other functionalities to this electronic commerce store. Some include gift cards, tax management, &lt;a href="https://docs.medusajs.com/modules/carts-and-checkout/backend/add-fulfillment-provider" rel="noopener noreferrer"&gt;custom shipping methods&lt;/a&gt;, and &lt;a href="https://docs.medusajs.com/plugins/notifications/" rel="noopener noreferrer"&gt;Notifications&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Go to the &lt;a href="https://docs.medusajs.com/quickstart/quick-start" rel="noopener noreferrer"&gt;Medusa documentation&lt;/a&gt; for more details regarding these functionalities and services.&lt;/p&gt;

&lt;p&gt;This article was originally published on the &lt;a href="https://www.learn-dev-tools.blog/how-to-build-an-electronic-commerce-store-with-medusa-and-nextjs/" rel="noopener noreferrer"&gt;Learn Dev Tools Blog&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>beginners</category>
      <category>ecommerce</category>
      <category>webdev</category>
    </item>
    <item>
      <title>7 Useful Mobile Applications to Improve Your Productivity as a Developer</title>
      <dc:creator>Gun</dc:creator>
      <pubDate>Tue, 04 Jul 2023 20:37:08 +0000</pubDate>
      <link>https://dev.to/gunkev/7-useful-mobile-applications-to-improve-your-productivity-as-a-developer-45p2</link>
      <guid>https://dev.to/gunkev/7-useful-mobile-applications-to-improve-your-productivity-as-a-developer-45p2</guid>
      <description>&lt;p&gt;Today many things that were usually done on a desktop, can now be performed on your smartphone thanks to the evolution of Mobile phones. Developers must be always on the move in today's fast-paced world, working on their projects and remaining productive. Mobile apps, which provide ease and flexibility, have become an essential element of a developer's arsenal. Having the appropriate mobile apps may dramatically enhance your productivity, whether you're coding on the move or managing your projects remotely. In this article, we'll look at the best mobile apps that every developer should use.&lt;/p&gt;

&lt;h2 id="heading-1-code-from-anywhere-with-replit"&gt;&lt;a href="https://gunkev.hashnode.dev/7-useful-mobile-applications-to-improve-your-productivity-as-a-developer#heading-1-code-from-anywhere-with-replit" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="heading-1-code-from-anywhere-with-replit"&gt;1. Code from Anywhere with Replit&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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501306208%2Fda27ce0c-bc94-44a3-9255-03ed3a6d9ae8.jpeg%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" 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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501306208%2Fda27ce0c-bc94-44a3-9255-03ed3a6d9ae8.jpeg%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" alt="" width="1076" height="2295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Coding from anywhere and doing everything you'd normally do on a desktop, such as coding the complete project, sharing, and hosting, are all possible. Everything is handled via your phone. Replit comes with everything you need to get started, including a CPU, storage, and RAM. You can interact with the terminal, use a file system, and do other things. Replit is available for &lt;a href="https://play.google.com/store/apps/details?id=com.replit.app&amp;amp;hl=en&amp;amp;gl=US" rel="noreferrer noopener"&gt;Android&lt;/a&gt; and &lt;a href="https://apps.apple.com/us/app/replit-code-anything/id1614022293" rel="noreferrer noopener"&gt;iOS&lt;/a&gt; systems&lt;/p&gt;

&lt;h2 id="heading-2-improve-your-productivity-with-macrodroid"&gt;&lt;a href="https://gunkev.hashnode.dev/7-useful-mobile-applications-to-improve-your-productivity-as-a-developer#heading-2-improve-your-productivity-with-macrodroid" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="heading-2-improve-your-productivity-with-macrodroid"&gt;2. Improve Your Productivity with Macrodroid&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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501316463%2F78debcf0-fe0d-4f1b-aac4-bf9301ce87b3.jpeg%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" 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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501316463%2F78debcf0-fe0d-4f1b-aac4-bf9301ce87b3.jpeg%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" alt="" width="1066" height="2242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One feature that Android consumers like is freedom. They are free to do whatever they choose. You can do even more using Macrodroid. It is one of the best Android device automation tools. You can auto-skip YouTube advertising, auto-swipe in TikTok, do something when something happens, and so on. The app is readily available for &lt;a href="https://play.google.com/store/apps/details?id=com.arlosoft.macrodroid&amp;amp;hl=en&amp;amp;gl=US" rel="noreferrer noopener"&gt;Android&lt;/a&gt; users only&lt;/p&gt;

&lt;h2 id="heading-3-test-your-apis-directly-with-your-mobile-phone-using-api-tester"&gt;&lt;a href="https://gunkev.hashnode.dev/7-useful-mobile-applications-to-improve-your-productivity-as-a-developer#heading-3-test-your-apis-directly-with-your-mobile-phone-using-api-tester" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="heading-3-test-your-apis-directly-with-your-mobile-phone-using-api-tester"&gt;3. Test your APIs directly with Your Mobile Phone using API Tester&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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501330688%2F36388484-4dd9-420c-a02b-0907fb89c0a6.jpeg%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" 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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501330688%2F36388484-4dd9-420c-a02b-0907fb89c0a6.jpeg%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" alt="" width="1080" height="2295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;API Tester is a robust tool for testing and debugging APIs from mobile devices. It has most of the functionalities of a typical desktop API testing program. You can test &lt;a href="https://www.learn-dev-tools.blog/creating-a-custom-api-with-strapi/" rel="noreferrer noopener"&gt;any API&lt;/a&gt;, including REST, GraphQL, WebSocket, and SOAP. It uses cURL or files from your device to import requests and collections. You can also run all queries simultaneously, save variables for later use, and integrate with other Widgets. API Tester is available on both &lt;a href="https://apps.apple.com/us/app/replit-code-anything/id1614022293" rel="noreferrer noopener"&gt;AppStore&lt;/a&gt; and &lt;a href="https://play.google.com/store/apps/details?id=apitester.org&amp;amp;hl=en&amp;amp;gl=US" rel="noreferrer noopener"&gt;PlayStore&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="heading-4-share-your-thoughts-easily-with-mindly"&gt;&lt;a href="https://gunkev.hashnode.dev/7-useful-mobile-applications-to-improve-your-productivity-as-a-developer#heading-4-share-your-thoughts-easily-with-mindly" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="heading-4-share-your-thoughts-easily-with-mindly"&gt;4. Share Your Thoughts Easily with Mindly&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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501330688%2F36388484-4dd9-420c-a02b-0907fb89c0a6.jpeg" 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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501330688%2F36388484-4dd9-420c-a02b-0907fb89c0a6.jpeg" alt="" width="800" height="1700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Share your work with friends, coworkers, or team members while maintaining control over viewing and editing rights. Collaborate whether you're at home, in your workplace, or on the other side of the world. With a single click, Mindly can convert your well-structured Word, &lt;a href="https://www.learn-dev-tools.blog/what-is-html-start-learning-web-development/" rel="noreferrer noopener"&gt;HTML&lt;/a&gt;, or Markdown document into a creative and entertaining mind map. Anytime you add, delete, or relocate nodes, your map automatically updates and preserves its layout. You can even import and export to or from your Dropbox account.&lt;/p&gt;

&lt;p&gt;The app is readily available on &lt;a href="https://apps.apple.com/us/app/mindly-mind-mapping/id771221376" rel="noreferrer noopener"&gt;AppStore&lt;/a&gt; and &lt;a href="https://play.google.com/store/apps/details?id=com.dripgrind.mindly&amp;amp;hl=en&amp;amp;gl=US" rel="noreferrer noopener"&gt;PlayStore&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="heading-5-avoid-painless-configurations-with-expo-go"&gt;&lt;a href="https://gunkev.hashnode.dev/7-useful-mobile-applications-to-improve-your-productivity-as-a-developer#heading-5-avoid-painless-configurations-with-expo-go" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="heading-5-avoid-painless-configurations-with-expo-go"&gt;5. Avoid Painless Configurations with Expo Go&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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501341607%2F2bcbb9f5-79bd-4e7f-9315-38863d9bd91f.jpeg%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" 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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501341607%2F2bcbb9f5-79bd-4e7f-9315-38863d9bd91f.jpeg%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" alt="" width="1080" height="2205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Expo Go will be extremely beneficial for React Native developers or anyone on their way to want to learn React Native. Rather than installing and configuring Android Studio locally for development. Simply use Expo Go to see real-time changes and easily share your creation with everyone. Create a react-native project with appropriate configurations, then scan the QR code from the console. The Expo Go smartphone app will provide access to the project. All your changes will be directly reflected in the app. You can download the app on &lt;a href="https://play.google.com/store/apps/details?id=host.exp.exponent&amp;amp;hl=en&amp;amp;gl=US" rel="noreferrer noopener"&gt;PlayStore&lt;/a&gt; or &lt;a href="https://apps.apple.com/us/app/expo-go/id982107779" rel="noreferrer noopener"&gt;AppStore&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="heading-6-stay-up-to-date-with-devbytes"&gt;&lt;a href="https://gunkev.hashnode.dev/7-useful-mobile-applications-to-improve-your-productivity-as-a-developer#heading-6-stay-up-to-date-with-devbytes" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="heading-6-stay-up-to-date-with-devbytes"&gt;6. Stay up to date with DevBytes&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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501348628%2Fe817137e-07c7-4a7e-938c-7b1d96e4ed0c.jpeg%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" 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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501348628%2Fe817137e-07c7-4a7e-938c-7b1d96e4ed0c.jpeg%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" alt="" width="1066" height="2261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a developer, you must constantly stay up to date on the latest technological developments. However, most of you do not have time to investigate and read lengthy articles. DevBytes explores, resumes and presents popular computer programming news. Not only can you obtain news, but you can also learn new coding ideas with explanations and code samples, find career possibilities, and participate anonymously in public polls to express your opinions. The application is only available on &lt;a href="https://play.google.com/store/apps/details?id=com.candelalabs.devbytes&amp;amp;hl=en&amp;amp;gl=US" rel="noreferrer noopener"&gt;PlayStore&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="heading-7-plan-easily-with-planio"&gt;&lt;a href="https://gunkev.hashnode.dev/7-useful-mobile-applications-to-improve-your-productivity-as-a-developer#heading-7-plan-easily-with-planio" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="heading-7-plan-easily-with-planio"&gt;7. Plan Easily with Planio&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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501367231%2F8b081135-9c7d-45e6-a45d-56d118b48301.png%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" 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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1688501367231%2F8b081135-9c7d-45e6-a45d-56d118b48301.png%3Fauto%3Dcompress%2Cformat%26format%3Dwebp" alt="" width="1242" height="2208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Planio implements scrum and agile methods. On the Agile Kanban-style board, you can plan sprints and track progress. However, more standard project management tools such as milestones and the Gantt chart can also be used. This is useful when you cannot manage everything using agile methodologies. As your team completes milestones, they'll start coming up with brilliant ideas. You want to debate them, evaluate their merits, and record your team's findings. &lt;a href="https://play.google.com/store/apps/details?id=io.plan.app.cordova&amp;amp;hl=en&amp;amp;gl=US" rel="noreferrer noopener"&gt;Planio&lt;/a&gt; also, allows you to construct your wiki, discuss ideas in a forum atmosphere, or simply leave comments on, well, just about anything. Work from anywhere at any time.&lt;/p&gt;

&lt;h2 id="heading-conclusion"&gt;&lt;a href="https://gunkev.hashnode.dev/7-useful-mobile-applications-to-improve-your-productivity-as-a-developer#heading-conclusion" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="heading-conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Mobile apps have transformed the way developers work and increased their productivity. Developers can code, collaborate, manage projects, and stay connected right from their mobile apps. Their simplicity and flexibility can enable developers to be productive even while they are on the go. Including these top mobile apps in their toolset will certainly improve the development process and yield better results.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>developer</category>
      <category>developertools</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Localize Your Vue Application with Tolgee?</title>
      <dc:creator>Gun</dc:creator>
      <pubDate>Thu, 29 Jun 2023 12:34:45 +0000</pubDate>
      <link>https://dev.to/gunkev/how-to-localize-your-vue-application-with-tolgee-41aa</link>
      <guid>https://dev.to/gunkev/how-to-localize-your-vue-application-with-tolgee-41aa</guid>
      <description>&lt;p&gt;Nowadays, providing solutions to specific problems identified in a domain has fueled the creation and growth of many companies and startups. But one problem arises which is how to market the product in various places given that not everyone does not have the same background. Even though English is one of the most used languages, especially in the professional domain, this does not exclude the other part of the population that uses other languages. To build an application or solution for a specific area, you need to assure that your solution will &lt;strong&gt;efficiently communicate with the target audience&lt;/strong&gt;. &lt;a href="https://tolgee.io/blog/benefits-challenges-software-localization" rel="noreferrer noopener"&gt;Software localization&lt;/a&gt; is the process of adapting a product or a service offered by the software to meet the needs of a desired culture, population or language.&lt;/p&gt;

&lt;p&gt;In this article, you will learn how to &lt;strong&gt;localize your Vue application in various languages&lt;/strong&gt; with Tolgee an open-source tool. But before that let’s have an overview of what Tolgee and Vuejs are.&lt;/p&gt;

&lt;h2 id="heading-what-is-tolgee"&gt;&lt;a href="https://gunkev.hashnode.dev/how-to-localize-your-vue-application-with-tolgee#heading-what-is-tolgee" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="heading-what-is-tolgee"&gt;What is Tolgee?&lt;/h2&gt;

&lt;p&gt;Tolgee is an &lt;strong&gt;open-source platform&lt;/strong&gt; that provides a user-friendly interface for organizing translations for developers and translators. It facilitates the translation process by providing a centralized site for all translation duties, such as managing translation files, translating text, and integrating with popular development tools &lt;strong&gt;such as Nextjs, Gatsby or Angular.&lt;/strong&gt; Tolgee also provides a number of tools to aid &lt;a href="https://tolgee.io/features/collaboration" rel="noreferrer noopener"&gt;collaborative translation operations&lt;/a&gt;, including translation memory. Tolgee is an &lt;strong&gt;all-in-one solution&lt;/strong&gt; for project management and optimizing multilingual content distribution.&lt;/p&gt;

&lt;h2 id="heading-what-is-vuejs"&gt;&lt;a href="https://gunkev.hashnode.dev/how-to-localize-your-vue-application-with-tolgee#heading-what-is-vuejs" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="heading-what-is-vuejs"&gt;What is Vue.js?&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://vuejs.org" rel="noreferrer noopener"&gt;Vue.js&lt;/a&gt; is a JavaScript framework used for creating user interfaces, PWA and single-page apps. It is intended to be adopted in stages and can also serve as a web application framework capable of powering complex single-page applications. Vue.js is well-known for its ease of use and simplicity, making it a popular choice among developers for creating scalable and performant web apps.&lt;/p&gt;

&lt;h2 id="heading-how-to-integrate-tolgee-into-a-vue-application"&gt;&lt;a href="https://gunkev.hashnode.dev/how-to-localize-your-vue-application-with-tolgee#heading-how-to-integrate-tolgee-into-a-vue-application" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id="heading-how-to-integrate-tolgee-into-a-vue-application"&gt;How to Integrate Tolgee into a Vue Application&lt;/h2&gt;

&lt;p&gt;This selection highlights a step-by-step approach to integrating Togee into a Vue application.&lt;/p&gt;

&lt;h3 id="heading-step-1-install-tolgee"&gt;&lt;a href="https://gunkev.hashnode.dev/how-to-localize-your-vue-application-with-tolgee#heading-step-1-install-tolgee" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="heading-step-1-install-tolgee"&gt;Step 1: Install Tolgee&lt;/h3&gt;

&lt;p&gt;You can either create a Tolgee project via the &lt;a href="https://app.tolgee.io/login" rel="noreferrer noopener"&gt;Tolgee Cloud Platform&lt;/a&gt; or self-host it. In this tutorial, we will use the hosted version of Tolgee. However, you can host the server on your own via Docker. If you don’t have Docker installed use this Docker &lt;a href="https://docs.docker.com/desktop/" rel="noreferrer noopener"&gt;quick guide&lt;/a&gt; to get started&lt;/p&gt;

&lt;p&gt;It is recommended to run it in a single container.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker run -v tolgee_data:/data/ -p 8085:8080 tolgee/tolgee
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then follow &lt;a href="https://tolgee.io/platform/self_hosting/running_with_docker" rel="noreferrer noopener"&gt;this guide&lt;/a&gt; to configure your Tolgee project.&lt;/p&gt;

&lt;h3 id="heading-step-2-create-a-project"&gt;&lt;a href="https://gunkev.hashnode.dev/how-to-localize-your-vue-application-with-tolgee#heading-step-2-create-a-project" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="heading-step-2-create-a-project"&gt;Step 2: Create a Project&lt;/h3&gt;

&lt;p&gt;Once everything is set up, log into your account and create a new project. Give a name to your project and &lt;strong&gt;add the languages&lt;/strong&gt; you wish your app to use. In this tutorial, the app will use three languages: English, French and Spanish&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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2Axr9RL729DkAd6Dfb.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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2Axr9RL729DkAd6Dfb.png" alt="" width="700" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="heading-step-3-generate-api-key"&gt;&lt;a href="https://gunkev.hashnode.dev/how-to-localize-your-vue-application-with-tolgee#heading-step-3-generate-api-key" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="heading-step-3-generate-api-key"&gt;Step 3: Generate API Key&lt;/h3&gt;

&lt;p&gt;Still, in your Tolgee dashboard, navigate to &lt;code&gt;integrate&lt;/code&gt;; the last element in the left menu and select Vue among the list of app integrations&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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2ADdnpqhKL4MkmfVv-.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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2ADdnpqhKL4MkmfVv-.png" alt="" width="700" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then in the select input, click on &lt;code&gt;Create new&lt;/code&gt; to create a new API Key.&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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2ALtvjIf230R4oWbao.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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2ALtvjIf230R4oWbao.png" alt="" width="700" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Name your API key and select the period of time you wish to use it and click on Save.&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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2AloWX2fNR1L3um1es.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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2AloWX2fNR1L3um1es.png" alt="" width="700" height="820"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will generate a first-time-only token that you need to save somewhere on your PC because you will use it later.&lt;/p&gt;

&lt;h3 id="heading-step-4-add-tolgee-to-your-vue-application"&gt;&lt;a href="https://gunkev.hashnode.dev/how-to-localize-your-vue-application-with-tolgee#heading-step-4-add-tolgee-to-your-vue-application" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="heading-step-4-add-tolgee-to-your-vue-application"&gt;Step 4: Add Tolgee to Your Vue application&lt;/h3&gt;

&lt;p&gt;Navigate to your desired directory and run the following command :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;npm init vue@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You will be prompted to provide your project’s name and other information.&lt;/p&gt;

&lt;p&gt;If you already have a project setup, you can skip this step.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Vue.js - The Progressive JavaScript Framework
√ Project name: ... vue-tolgee
√ Add TypeScript? ... No / Yes
√ Add JSX Support? ... No / Yes
√ Add Vue Router for Single Page Application development? ... No / Yes
√ Add Pinia for state management? ... No / Yes
√ Add Vitest for Unit Testing? ... No / Yes
√ Add an End-to-End Testing Solution? » No
√ Add ESLint for code quality? ... No / Yes
√ Add Prettier for code formatting? ... No / Yes
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once the command is executed, navigate to the project’s root directory and run &lt;code&gt;npm install&lt;/code&gt;, then &lt;code&gt;npm run dev&lt;/code&gt; to start the app.&lt;/p&gt;

&lt;p&gt;If your application does not automatically open, type &lt;code&gt;[&lt;/code&gt;&lt;a href="https://gunkev.hashnode.dev/%20raw%20`[`%20endraw%20http://localhost:5173%20raw%20`](http://localhost:5173)`%20endraw%20" rel="noreferrer noopener"&gt;&lt;code&gt;localhost:5173&lt;/code&gt;](&lt;/a&gt;&lt;a href="http://localhost:5173" rel="noreferrer noopener"&gt;localhost:5173&lt;/a&gt;)`` in your browser to visualise your freshly created application.&lt;/p&gt;

&lt;p&gt;If your project is a Vue 3 Typescript project create &lt;code&gt;shims-vue.d.ts&lt;/code&gt; file and add the following code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/* eslint-disable */
declare module '*.vue' {
  import type { DefineComponent } from 'vue';
  const component: DefineComponent&amp;lt;{}, {}, any&amp;gt;;
  export default component;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will allow you to import vue components.&lt;/p&gt;

&lt;h3 id="heading-step-5-install-tolgee-package"&gt;&lt;a href="https://gunkev.hashnode.dev/how-to-localize-your-vue-application-with-tolgee#heading-step-5-install-tolgee-package" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="heading-step-5-install-tolgee-package"&gt;Step 5: Install Tolgee Package&lt;/h3&gt;

&lt;p&gt;Now use the command below to install Tolgee.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cd vue-tolgee
npm install @tolgee/vue
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="heading-step-6-add-environment-variable"&gt;&lt;a href="https://gunkev.hashnode.dev/how-to-localize-your-vue-application-with-tolgee#heading-step-6-add-environment-variable" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="heading-step-6-add-environment-variable"&gt;Step 6: Add Environment Variable:&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;env.development.local&lt;/code&gt; file if you don’t have one at the root of your project and paste the following code&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;VUE_APP_TOLGEE_API_URL=&amp;lt;host&amp;gt;
VUE_APP_TOLGEE_API_KEY=&amp;lt;Only new key can be revealed&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;VUE_APP_TOLGEE_API_URL&lt;/code&gt; is where your Tolgee server is hosted&lt;/p&gt;

&lt;p&gt;&lt;code&gt;VUE_APP_TOLGEE_API_KEY&lt;/code&gt; is your API key. If somehow you lost the API key you generated earlier, you can regenerate a new one.&lt;/p&gt;

&lt;p&gt;Click on your profile picture at the top right corner of the dashboard&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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2A3Xs9Q3NXevRUiEol.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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2A3Xs9Q3NXevRUiEol.png" alt="" width="700" height="1141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then &lt;code&gt;Select Project API Keys&lt;/code&gt;. In the new window that opens, either you add a new API key or you generate a new one. Make sure to save the API key somewhere this time in case you failed to do it previously.&lt;/p&gt;

&lt;p&gt;Once your API key is ready, you can now add it to your env file.&lt;/p&gt;

&lt;h3 id="heading-step-7-initialize-tolgee"&gt;&lt;a href="https://gunkev.hashnode.dev/how-to-localize-your-vue-application-with-tolgee#heading-step-7-initialize-tolgee" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="heading-step-7-initialize-tolgee"&gt;Step 7: Initialize Tolgee&lt;/h3&gt;

&lt;p&gt;Update your &lt;code&gt;main.ts&lt;/code&gt; found at the root of your project with the following code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import './assets/main.css';
import { createApp } from 'vue';
import App from './App.vue';
import {
  BackendFetch,
  DevTools,
  Tolgee,
  FormatSimple,
  VueTolgee,
} from '@tolgee/vue';

const tolgee = Tolgee()
  .use(DevTools())
  .use(FormatSimple())
  .use(BackendFetch())
  .init({
    language: 'en',
    apiUrl: import.meta.env.VITE_TOLGEE_API_URL,
    apiKey: import.meta.env.VITE_TOLGEE_API_KEY,
  });
const app = createApp(App);
app.use(VueTolgee, { tolgee });
app.mount('#app');
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then wrap your code in &lt;code&gt;src/app.vue&lt;/code&gt; in &lt;code&gt;TolgeeProvider&lt;/code&gt; tag like below:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;TolgeeProvider&amp;gt;
    &amp;lt;header&amp;gt;
      &amp;lt;img
        alt="Vue logo"
        class="logo"
        src="./assets/logo.svg"
        width="125"
        height="125"
      /&amp;gt;
      &amp;lt;div class="wrapper"&amp;gt;
        &amp;lt;HelloWorld msg="You did it!" /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/header&amp;gt;
    &amp;lt;main&amp;gt;
      &amp;lt;TheWelcome /&amp;gt;
    &amp;lt;/main&amp;gt;
  &amp;lt;/TolgeeProvider&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="heading-step-8-add-translations"&gt;&lt;a href="https://gunkev.hashnode.dev/how-to-localize-your-vue-application-with-tolgee#heading-step-8-add-translations" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="heading-step-8-add-translations"&gt;Step 8: Add Translations&lt;/h3&gt;

&lt;p&gt;Now head to your Tolgee server and add some translations. In this tutorial, you will be translating the default Vue 3 starting application so for each text, add a keyname, and add the default English text. Make sure to &lt;a href="https://tolgee.io/blog/naming-translation-keys" rel="noreferrer noopener"&gt;conventionally name your translations&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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2AFItDSCk2-zF997h8.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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2AFItDSCk2-zF997h8.png" alt="" width="700" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then click on the French and Spanish input to select a translation from the &lt;a href="https://tolgee.io/blog/tolgee-ai-translator" rel="noreferrer noopener"&gt;machine-generated translations&lt;/a&gt; that best describes the text. You can adjust if you feel that the text does not fit its context.&lt;/p&gt;

&lt;p&gt;Repeat this process until all translations are added. In the end, you should see the list of translations below.&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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2Ac3Efn7wpzP69xDNk.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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2Ac3Efn7wpzP69xDNk.png" alt="" width="700" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="heading-step-9-create-a-select-component"&gt;&lt;a href="https://gunkev.hashnode.dev/how-to-localize-your-vue-application-with-tolgee#heading-step-9-create-a-select-component" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="heading-step-9-create-a-select-component"&gt;Step 9: Create a Select Component&lt;/h3&gt;

&lt;p&gt;The select component will help you easily switch from one language to another. In your &lt;code&gt;src/components&lt;/code&gt; created &lt;code&gt;selector.vue&lt;/code&gt; file and add the following code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { useTolgee } from '@tolgee/vue'
const tolgee = useTolgee(['language'])
const changeLanguage = (e) =&amp;gt; {
  tolgee.value.changeLanguage(e.target.value)
}
&amp;lt;/script&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;select :value="tolgee.getLanguage()" v-on:change="changeLanguage" class="selector"&amp;gt;
    &amp;lt;option value="en"&amp;gt;🇬🇧 English&amp;lt;/option&amp;gt;
    &amp;lt;option value="fr"&amp;gt;🇫🇷 Français&amp;lt;/option&amp;gt;
    &amp;lt;option value="es"&amp;gt;🇪🇸 Español&amp;lt;/option&amp;gt;
  &amp;lt;/select&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now let’s give some style to the select tag with the following code in &lt;code&gt;src/assets.main.css&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;.selector {
  display: block;
  text-align: center;
  width: 80%;
  padding: 10px;
  border-radius: 5px;
  font-size: 18px;
  border: none;
  background: rgba(0, 255, 255, 0.51);
  -webkit-appearance: none;
  -moz-appearance: none;
  text-indent: 1px;
  text-overflow: '';
  cursor: pointer;
  margin-left: 10px;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id="heading-step-10-add-translation-keynames"&gt;&lt;a href="https://gunkev.hashnode.dev/how-to-localize-your-vue-application-with-tolgee#heading-step-10-add-translation-keynames" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3 id="heading-step-10-add-translation-keynames"&gt;Step 10: Add Translation Keynames&lt;/h3&gt;

&lt;p&gt;This is the last step which consists of adding translation keynames. A typical translation looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import { T } from '@tolgee/vue';

&amp;lt;T keyName="documentation" defaultValue="Documentation" /&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So in your &lt;code&gt;src/components/TheWelcome.vue&lt;/code&gt; replace your code with the following:&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;src/components/HelloWorld.vue&lt;/code&gt; add:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;script setup lang="ts"&amp;gt;
import { T } from '@tolgee/vue'
...
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div class="greetings"&amp;gt;
   ...
    &amp;lt;h3&amp;gt;
      &amp;lt;T keyName="success" defaultValue="You've successfully created a project with Vite + Vue 3" /&amp;gt;
    &amp;lt;/h3&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Finally, update your &lt;code&gt;App.vue&lt;/code&gt; file with the code below:&lt;/p&gt;

&lt;p&gt;In the code block above, we have updated our components imports and added the &lt;code&gt;Selector&lt;/code&gt; component we created earlier.&lt;/p&gt;

&lt;p&gt;But there is one last thing we need to do, which is to add our translation files to the Vue project.&lt;/p&gt;

&lt;p&gt;In your Tolgee dashboard, click on export in the left menu, then select the languages you wish to use and click on export.&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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2AGh2Nh66jn-M2YRBj.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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2AGh2Nh66jn-M2YRBj.png" alt="" width="700" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create i18n folder in your Vue application public folder and add the extracted files&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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2AqUu9223BpJ7mwSSy.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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A700%2F0%2AqUu9223BpJ7mwSSy.png" alt="" width="700" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;utilis/i18n/en.json&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  "documentation": "Documentation",
  "documentation_desc": "Vue’s official documentation provides you with all information you need to get started.",
  "tooling": "Tooling",
  "tooling_desc": "This project is served and bundled with Vite. The recommended IDE setup is VSCode + Volar. If you need to test your components and web pages, check out Cypress and Cypress Component Testing. More instructions are available in README.md.",
  "ecosystem": "Ecosystem",
  "ecosystem_desc": "Get official tools and libraries for your project: Pinia, Vue Router, Vue Test Utils, and Vue Dev Tools. If you need more resources, we suggest paying Awesome Vue a visit. ",
  "community": "Community",
  "community_desc": "Got stuck? Ask your question on Vue Land, our official Discord server, or StackOverflow. You should also subscribe to our mailing list and follow the official @vuejs twitter account for latest news in the Vue world. ",
  "support Vue": "Support Vue",
  "support Vue_desc": "As an independent project, Vue relies on community backing for its sustainability. You can help us by becoming a sponsor. "
    "readme": "More instructions are available in README.md.",
  "success": "You’ve successfully created a project with Vite + Vue 3",
  "hello": "Hello"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;utilis/i18n/fr.json&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  "documentation": "Documentación",
  "documentation_desc": "La documentation officielle de Vue vous fournit toutes les informations dont vous avez besoin pour commencer.",
  "tooling": "Outillage",
  "tooling_desc": "Ce projet est servi et groupé avec Vite. La configuration IDE recommandée est VSCode + Volar. Si vous avez besoin de tester vos composants et vos pages Web, consultez Cypress et Cypress Component Testing. Plus d'instructions sont disponibles dans README.md.",
  "ecosystem": "Écosystème",
  "ecosystem_desc": "Obtenez des outils et des bibliothèques officiels pour votre projet : Pinia, Vue Router, Vue Test Utils et Vue Dev Tools. Si vous avez besoin de plus de ressources, nous vous suggérons de rendre visite à Awesome Vue.",
  "community": "Communauté",
  "community_desc": "Est resté coincé? Posez votre question sur Vue Land, notre serveur Discord officiel, ou StackOverflow. Vous devez également vous inscrire à notre liste de diffusion et suivre le compte Twitter officiel @vuejs pour les dernières nouvelles du monde Vue.",
  "support Vue": "Soutenir Vue",
  "support Vue_desc": "En tant que projet indépendant, Vue s'appuie sur le soutien de la communauté pour sa durabilité. Vous pouvez nous aider en devenant parrain.",
  "readme": "Plus d'instructions sont disponibles dans README.md.",
  "success": "Vous avez réussi à créer un projet avec Vite + Vue 3",
  "hello": "Bonjour"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;utilis/i18n/es.json&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  "documentation": "Documentación",
  "documentation_desc": "La documentación oficial de Vue le brinda toda la información que necesita para comenzar.",
  "tooling": "Estampación",
  "tooling_desc": "Este proyecto se sirve y se incluye con Vite. La configuración IDE recomendada es VSCode + Volar. Si necesita probar sus componentes y páginas web, consulte Cypress y Cypress Component Testing. Más instrucciones están disponibles en README.md.",
  "ecosystem": "Ecosistema",
  "ecosystem_desc": "Obtenga herramientas y bibliotecas oficiales para su proyecto: Pinia, Vue Router, Vue Test Utils y Vue Dev Tools. Si necesita más recursos, le sugerimos que visite Awesome Vue. ",
  "community": "Comunidad",
  "community_desc": "¿Quedó atascado? Haga su pregunta en Vue Land, nuestro servidor oficial de Discord o StackOverflow. También debe suscribirse a nuestra lista de correo y seguir la cuenta oficial de Twitter @vuejs para conocer las últimas noticias en el mundo de Vue. ",
  "support Vue": "Soporte Vue",
  "support Vue_desc": "Como proyecto independiente, Vue depende del respaldo de la comunidad para su sostenibilidad. Puedes ayudarnos convirtiéndote en patrocinador. ",
  "readme": "Hay más instrucciones disponibles en README.md.",
  "success": "Ha creado con éxito un proyecto con Vite + Vue 3",
  "hello": "Hola"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you re-run your application, it should be working perfectly like the screenshot below:&lt;/p&gt;

&lt;p&gt;You can find the full working code in this &lt;a href="https://github.com/Gunkev/vue-tolgee/tree/main" rel="noreferrer noopener"&gt;GitHub Repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>opensource</category>
      <category>beginners</category>
      <category>translation</category>
    </item>
    <item>
      <title>What is Open Source Software? A Beginner’s Guide</title>
      <dc:creator>Gun</dc:creator>
      <pubDate>Thu, 29 Jun 2023 12:27:56 +0000</pubDate>
      <link>https://dev.to/gunkev/what-is-open-source-software-a-beginners-guide-5o9</link>
      <guid>https://dev.to/gunkev/what-is-open-source-software-a-beginners-guide-5o9</guid>
      <description>&lt;h2&gt;Introduction&lt;/h2&gt;

&lt;p&gt;If you have been in the tech industry for a while you may have heard about open source or open source software. When we talk about open source, you should think of open collaboration, open exchange, participation transparency and community-oriented development.&lt;/p&gt;

&lt;p&gt;In this article, you will learn more about open source software including the difference between open source and other forms of software, key features of OSS, pros and cons of open source, how to start contributing to open source and some examples of OSS.&lt;/p&gt;

&lt;h2&gt;What is Open Source?&lt;/h2&gt;

&lt;p&gt;Open source designed any program whose source code can be modified and shared by anyone because it’s openly accessible.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;What is Source Code?&lt;/h2&gt;

&lt;p&gt;A source code is a set of instructions written in the form of functions, descriptions, methods, calls or any other operational statement by a programmer using a computer programming language. A source code can be open that is available to anyone to edit and redistribute or closed. Source code is the part that most software or computer users cannot see.&lt;/p&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%2Fwww.learn-dev-tools.blog%2Fwp-content%2Fuploads%2F2023%2F03%2Fsource-code-medusa-js-github-1024x567.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%2Fwww.learn-dev-tools.blog%2Fwp-content%2Fuploads%2F2023%2F03%2Fsource-code-medusa-js-github-1024x567.png" alt="Example of a Source code taken from Medusa's GitHub Repository " width="800" height="442"&gt;&lt;/a&gt;Example of a Source code taken from Medusa's GitHub Repository &lt;p&gt;&lt;/p&gt;



&lt;h2&gt;What Does Open Source Software(OSS) Mean?&lt;/h2&gt;

&lt;p&gt;The term Open source software is a non-proprietary software in which code is publicly available to anyone to inspect, comment on, modify, improve and redistribute it. Open source software is created and maintained by a group of developers so if you are new to computer programming, you can begin your journey as an open source contributor.&lt;/p&gt;

&lt;p&gt;I have known open source since 2017 and started with my first contribution in 2020. I made my first contributions to the &lt;a href="https://github.com/apache" rel="noreferrer noopener"&gt;Apache Software Foundation&lt;/a&gt; Gihhub and &lt;a href="https://github.com/mozilla-mobile" rel="noreferrer noopener"&gt;Mozilla mobile Github&lt;/a&gt;.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;What is the Difference between Open source Software and other Types of Software?&lt;/h2&gt;

&lt;p&gt;Do not get confused with open source, free, closed software and freeware. This section highlights the differences between these types of Software.&lt;/p&gt;

&lt;h3&gt;Open Source vs Free Software&lt;/h3&gt;

&lt;p&gt;Open source software is software available for anyone to edit, improve and redistribute. The source code is accessible to anyone. Open source promotes collaboration and sharing of source code. It’s an effective way for organizations, developers and individuals to collaborate. Freedom to use the software is not absolute or imposed but it’s allowed. Every open source software is not free. However, some open source licenses are restrictive. &lt;strong&gt;WordPress&lt;/strong&gt; is an example of open source software&lt;/p&gt;

&lt;p&gt;Free Software describes software in which source code is free to use, edit and redistribute without any restrictions. With free software, Freedom is a value that is more important than any other thing. All free software is open source software. &lt;strong&gt;Ares is an example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open source and free software have distinguished features. Some open source software is restrictive and all free software are open source software but not all open source software is free software.&amp;nbsp;&lt;/p&gt;

&lt;h3&gt;Open Source Software vs Freeware&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.learn-dev-tools.blog/what-is-a-free-tool-all-you-should-know/" rel="noopener noreferrer"&gt;Freeware&lt;/a&gt;&lt;/strong&gt; is any software that is free to use and available to the general public. If you are a, it can be frustrating to use freeware because its source code is not open or rather, it’s closed so you can edit the software to fit your needs. You can only use the features it offers even if it it is limited. Google Chrome is an example of freeware&lt;/p&gt;

&lt;p&gt;While with open source software, you have the possibility to edit, improve, and add different features to fit your needs. An example is Chromium.&lt;/p&gt;

&lt;p&gt;Freeware source code is closed, only the developer or organization who created the software can view its source code while open source one can edit and redistribute. Freeware is copyrighted while Open source is not.&lt;/p&gt;

&lt;h3&gt;Open Source vs Proprietary Software&lt;/h3&gt;

&lt;p&gt;You do not need an authenticated license to use open source software. They function with GNU Public License. They can be installed freely on any computer system. Open source software is developed and maintained through open collaboration.&lt;/p&gt;

&lt;p&gt;Proprietary Software is software with protected source code(closed). They cannot be installed on a computer without a valid license. So users need a valid and authenticated license to use it. They are also copyrighted like freeware but the difference is that you do not need to pay to use freeware. An example is Windows&lt;/p&gt;

&lt;h2&gt;Key features of Open Source Software&lt;/h2&gt;

&lt;p&gt;In this section, I will elaborate on the various key features and values that cover Open source Software.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Transparency: &lt;/strong&gt;Open source allows you to track and understand any changes made to software without relying on the software manufacturer.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Collaboration&lt;/strong&gt;: The main goal of the open source initiative is to allow anyone to freely participate and improve the software. By working together, we can easily identify and solve problems&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Meritocracy&lt;/strong&gt;: Including diverse perspectives from different brains can help identify the best ideas by decision-makers and could equally determine the success of a project.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Community&lt;/strong&gt;: When many people gather to help and share ideas, it creates a community where each other can get support.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Review&lt;/strong&gt;: Since source code is freely available to the public, different experimented community members can check, propose ideas and improve the software.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Reliability&lt;/strong&gt;: Many collaborators can update and test the code to check if there is any bug and fix it. Open sources do not depend on one, two or three people to maintain but rather have a large community that supports them.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Flexibility&lt;/strong&gt;: Flexibility refers to the fact that you can use open source code to address problems that are unique to your business. Plus you can rely on community help to improve an existing solution or implement a new one.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Lower cost:&lt;/strong&gt; Most of the time, open source is free to use and download. Sometimes you may need to pay for only reinforcing security and interoperability.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;What are the Benefits of Open Source?&lt;/h2&gt;

&lt;p&gt;Choosing to use or contribute to open source software offers many benefits. Some of them include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open source is free&lt;/li&gt;



&lt;li&gt;You have more control over the software. Compare to Closed software you can edit open source software to fit your needs.&lt;/li&gt;



&lt;li&gt;It's good for anyone beginning computer programming. As a beginner, you can become a better programmer by sharing your work and contributing to other works&lt;/li&gt;



&lt;li&gt;Open source is good because it can help to enhance software security as anyone can spot and fix a security issue quickly that the original programmer may have missed&lt;/li&gt;



&lt;li&gt;Open source projects tend to be more stable than other types of software since there is a great community behind ready-to-deploy means to improve it.&lt;/li&gt;



&lt;li&gt;It promotes collaboration through the creation of communities.&lt;/li&gt;



&lt;li&gt;Open source is flexible&lt;/li&gt;



&lt;li&gt;They have Good &lt;strong&gt;Documentation&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;What are the Disadvantages of Open Source Software?&lt;/h2&gt;

&lt;p&gt;Not everyone embraces the open source philosophy. Some organizations still debate the fact that open source software is more vulnerable since their source code is open to anyone.&lt;/p&gt;

&lt;p&gt;I have been in the software community for 3 to 4 years now and I have noticed that they tend to be more secure since issues linked to security or not can easily be identified and fixed by community members. However, I equally notice some drawbacks of open source.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open source software can be difficult to set up, especially for beginners. Some of them may lack user-friendly interfaces imposing you to use a &lt;strong&gt;Terminal&lt;/strong&gt;
&lt;/li&gt;



&lt;li&gt;Liability problems. Open source rarely contains warrant or liability protections&lt;/li&gt;



&lt;li&gt;Compatibility issues.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Initially, there is no cost but it can be costly in the long term especially if you need immediate support. There is also training cost in case you incorporate the software into your team.&lt;/li&gt;



&lt;li&gt;Not all open source possesses a user manual although you can seek help from the community it won't always be available immediately like commercial software where support is available 24h/7&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;How can you Start Contributing to Open Source?&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%2Fwww.learn-dev-tools.blog%2Fwp-content%2Fuploads%2F2023%2F03%2FHOW-TO-CONTRIBUTE-TO-OSS-1024x576.webp" 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%2Fwww.learn-dev-tools.blog%2Fwp-content%2Fuploads%2F2023%2F03%2FHOW-TO-CONTRIBUTE-TO-OSS-1024x576.webp" alt="how to contribute to OSS" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;It’s quite simple to contribute to open source software. You could do that by identifying and fixing bugs, adding features, improving documentation and &lt;strong&gt;&lt;a href="https://www.learn-dev-tools.blog/what-is-content-writing-an-easy-beginners-guide/" rel="noopener noreferrer"&gt;writing content&lt;/a&gt;&lt;/strong&gt; in the form of blog posts, technical tutorials or creating Videos. You also create your own open source project to promote open source.&lt;/p&gt;

&lt;p&gt;To contribute to existing open source software, you should head to &lt;strong&gt;&lt;a href="http://github.com" rel="noreferrer noopener"&gt;GitHub&lt;/a&gt;:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find a project and Fork&lt;/li&gt;



&lt;li&gt;Clone the Project to your Local machine&lt;/li&gt;



&lt;li&gt;Create a new branch&lt;/li&gt;



&lt;li&gt;Make changes&lt;/li&gt;



&lt;li&gt;Commit your changes&lt;/li&gt;



&lt;li&gt;Push your changes to your remote repository&lt;/li&gt;



&lt;li&gt;Finally, Create a pull request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you don’t have any project ideas, then open source projects to contribute to include&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/medusajs/medusa" rel="noreferrer noopener"&gt;Medusa&lt;/a&gt;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://github.com/ToolJet/ToolJet" rel="noreferrer noopener"&gt;Tooljet&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://github.com/doczjs/docz" rel="noreferrer noopener"&gt;Docz&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://github.com/boxyhq/jackson" rel="noreferrer noopener"&gt;SAML Jackson&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://github.com/chatwoot/chatwoot" rel="noreferrer noopener"&gt;Chatwoot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Open Source License&lt;/h2&gt;

&lt;p&gt;An Open source License governs how other individuals besides the initial developer can use, modify and redistribute the software. It allows the software to be transparent and changed by members of a community or third party. According to the &lt;a href="https://opensource.org/licenses/" rel="noreferrer noopener"&gt;Open Source Initiative&lt;/a&gt; include&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://opensource.org/license/apache-2-0/" rel="noreferrer noopener"&gt;Apache License, Version 2.0&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://opensource.org/license/epl-2-0/" rel="noreferrer noopener"&gt;Eclipse Public License version 2.0&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://opensource.org/license/cddl-1-0/" rel="noreferrer noopener"&gt;Common Development and Distribution License 1.0&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://opensource.org/license/lgpl-2-0/" rel="noreferrer noopener"&gt;GNU Library General Public License version 2&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://opensource.org/license/mpl-2-0/" rel="noreferrer noopener"&gt;Mozilla Public License 2.0 (MPL-2.0)&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://opensource.org/license/bsd-2-clause/" rel="noreferrer noopener"&gt;The 2-Clause BSD License&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Example of Open Source Software&lt;/h2&gt;

&lt;p&gt;You might have used an open source software without realising. There are many of them out there. Some popular and most-used open source software is&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;WordPress, an open source CMS for Content Management&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.learn-dev-tools.blog/what-is-the-flutter-framework-a-beginners-introduction-to-flutter/" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt;&lt;/strong&gt; for web, desktop and mobile development&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.learn-dev-tools.blog/what-is-a-css-framework-all-you-need-to-know/" rel="noopener noreferrer"&gt;CSS Frameworks&lt;/a&gt;&lt;/strong&gt; Like Bootstrap for website layout&lt;/li&gt;



&lt;li&gt;VLC Media Player for video and sound player&lt;/li&gt;



&lt;li&gt;Libre Office for word processing&lt;/li&gt;



&lt;li&gt;Open source Linux Operating System&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt; is open source too.&lt;/li&gt;



&lt;li&gt;Open source reporting tools include Metabase, BIRT&lt;/li&gt;



&lt;li&gt;Open source PDF viewer include Qoppa PDF Studio, pdfFiller, and Soda PDF&lt;/li&gt;



&lt;li&gt;Some Java open source projects are Arduino, Mindustry, and Signal Android&lt;/li&gt;



&lt;li&gt;We equally have open source development tools like Eclipse IDE, and Apache Cordova&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;FAQ&lt;/h2&gt;

&lt;h3&gt;Is Open Source Software Free?&lt;/h3&gt;

&lt;p&gt;Most open source software is free but there are some exceptions. Some open source licenses are restrictive and do not allow the creation of a modified version. An example is Open Source Watcom&lt;/p&gt;

&lt;h3&gt;Where can I find OSS?&lt;/h3&gt;

&lt;p&gt;You can find and download open source projects on SourceForge, OSDN, FossHub, GitHub, and F-Droid&lt;/p&gt;

&lt;h3&gt;Why Should you Contribute to OSS?&lt;/h3&gt;

&lt;p&gt;The most important advantage of contributing to open source is that you can find and collaborate with other developers hence improving your developer network.&lt;/p&gt;

&lt;h3&gt;Why is Open Source Important?&lt;/h3&gt;

&lt;p&gt;Open source improves innovations through collaboration with other organizations and developer&lt;/p&gt;

&lt;h3&gt;What are Some Open Source Competitions?&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Open Source Competitions&lt;/strong&gt; are contests where you can learn to participate in open source projects, demonstrate your coding skills, and get feedback. Examples of Open source competitions include Open Source Contest, &lt;a href="https://summerofcode.withgoogle.com/" rel="noreferrer noopener"&gt;Google Summer of Code (GSoC)&lt;/a&gt;, &lt;a href="https://www.outreachy.org/" rel="noreferrer noopener"&gt;Outreachy&lt;/a&gt;, &lt;a href="https://osoc.be/" rel="noreferrer noopener"&gt;Open Summer of Code&lt;/a&gt;, &lt;a href="https://www.fsf.org/volunteer/internships" rel="noreferrer noopener"&gt;Free Software Foundation (FSF) Internship Program&lt;/a&gt;, &lt;a href="https://gssoc.girlscript.tech/" rel="noreferrer noopener"&gt;GirlScript Summer of Code (GSSoC)&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;What is Closed Source Software?&lt;/h3&gt;

&lt;p&gt;Closed Software is software whose source code is only available to the author. So only the author can modify and copy it.&lt;/p&gt;

&lt;h3&gt;What is the Open Source Initiative?&lt;/h3&gt;

&lt;p&gt;The Open Source Initiative is a non-profit organisation with the main aim to educate about, form, and inform the benefit of open source&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Open source is available for anyone to edit, use and redistribute. Some key features of open source include transparency, reliability, available community and flexibility. However, OSS can be costly in a long run. So if you are planning to incorporate OSS into your business you need to include additional or urgent costs in your project plan including the cost of training staff members to use the software. Some popular Open source projects include Apache, WordPress, and Mozilla Firefox.&lt;/p&gt;

&lt;p&gt;If you like this kind of content, feel free to share or subscribe to our newsletter below to get fresh content like this periodically. But if you have any questions, you can reach me via &lt;a href="https://twitter.com/gunkev11oz" rel="noreferrer noopener"&gt;my twitter account&lt;/a&gt;. I will be glad to hear from you.&lt;/p&gt;



</description>
      <category>opensource</category>
      <category>softwareengineering</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Let's Flutter Together Today</title>
      <dc:creator>Gun</dc:creator>
      <pubDate>Thu, 06 Apr 2023 15:00:06 +0000</pubDate>
      <link>https://dev.to/gunkev/lets-flutter-together-today-3d9d</link>
      <guid>https://dev.to/gunkev/lets-flutter-together-today-3d9d</guid>
      <description>&lt;p&gt;Photo by &lt;a href="https://unsplash.com/es/@ruizra?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Randall  Ruiz&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/flutter?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nowadays mobile application is growing exponentially. Many people use smartphones these days either for automation, relaxation,  GPS for directions, keep track of appointments and contacts. In fact, many employees and businesses have trouble just imagining a day without a smartphone or mobile device.  As people evolve over time so are the different tools used to build most software found in smartphones. Please note that Flutter is also used to build web and desktop apps. Here I will only focus on mobile applications. Then What is Flutter- Why should you learn Flutter in 2023?&lt;/p&gt;

&lt;p&gt;In this article, I will briefly introduce Flutter to you and list some companies or apps which use or build applications using it.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://flutter.dev" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt; is an &lt;a href="https://www.learn-dev-tools.blog/what-does-open-source-software-mean-a-beginners-guide/" rel="noopener noreferrer"&gt;Open source&lt;/a&gt; toolkit used to build expressively beautiful, natively compiled applications in a single code base. One of its benefits and why I will encourage you to use it in your project is the fact that you can build amazing UI and fancy animations. You also have the possibility to build an app that will run on different operating systems. The factor is commonly known as cross-platform. But apart from this why should you learn Flutter in 2023?&lt;/p&gt;

&lt;h2&gt;Fast Development with Flutter&lt;/h2&gt;

&lt;p&gt;Flutter provides a feature known as hot reload which helps you to quickly build and test your UI as well as fix some bugs related to them without having to re-run the whole app. Flutter framework automatically rebuilds the widget tree in order for you to quickly view the effects of any changes.&lt;/p&gt;

&lt;h2&gt;Native Performance&lt;/h2&gt;

&lt;p&gt;Flutter’s widgets provide most of the native components such as scrolling, navigation, icons and fonts to provide full native performance on both iOS and Android.&lt;/p&gt;

&lt;h2&gt;Flutter, a Wonderful Documentation&lt;/h2&gt;

&lt;p&gt;Even if you are new to mobile development or not flutter offers highly detailed and comprehensive &lt;a href="https://www.learn-dev-tools.blog/an-easy-overview-of-software-documentation/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; to help developers or none developers acquire the knowledge needed to perform a particular task.&lt;/p&gt;

&lt;h2&gt;Ready-to-use tutorials&lt;/h2&gt;

&lt;p&gt;Truly if you are not still satisfied with the documentation you can go ahead and download tutorials on Udacity, Udemy or any other platform which offers complete mastery tools. These tutorials may help you polish up your skills since they usually contain practical projects.&lt;/p&gt;

&lt;h2&gt;Learn from Developers&lt;/h2&gt;

&lt;p&gt;Learning does not imply learning on your own. There are many developers with outstanding experience as well as forums such as Stack Overflow or GitHub forums to help. Just be humble to yourself and accept to receive and share knowledge with others.&lt;/p&gt;

&lt;h2&gt;Who is using Flutter?&lt;/h2&gt;

&lt;p&gt;I forget to mention it but Google is actually the one who is behind the Flutter framework and many companies around you may know actually use it. Some of those companies are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://google.com" rel="noopener noreferrer"&gt;Google&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;Groupon,&lt;/li&gt;
&lt;li&gt;EMAAR,&lt;/li&gt;
&lt;li&gt;UNbank,&lt;/li&gt;
&lt;li&gt;Ebay,&lt;/li&gt;
&lt;li&gt;Alibaba group,&lt;/li&gt;
&lt;li&gt;CapitalOne&lt;/li&gt;
&lt;li&gt;Tencent,&lt;/li&gt;
&lt;li&gt;Square,&lt;/li&gt;
&lt;li&gt;SONOS... and many other organizations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Some applications around us which are built using Flutter...&lt;/h3&gt;

&lt;p&gt;For example, we have,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Ads at the top of this list,&lt;/li&gt;
&lt;li&gt;Insight Timer,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.abbeyroad.com/" rel="noopener noreferrer"&gt;Abbey Road Studios,&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Baidu,&lt;/li&gt;
&lt;li&gt;ByteDance,&lt;/li&gt;
&lt;li&gt;Hamilton,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ahhh! I have just been talking about the positive aspects of Flutter. What about the drawbacks, Well Flutter has a handful number of disadvantages just as it has great abilities.&lt;/p&gt;

&lt;p&gt;So what are its drawbacks? Let's flutter together today to expose them. &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%2Fkr1se8eahtc6l3xpnlfu.gif" 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%2Fkr1se8eahtc6l3xpnlfu.gif" alt="Image description" width="346" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you were able to learn something from this article and wish this article will help you understand more and choose the right technology that will help you to build awesome apps either with or without Flutter. Take your time to choose and then get started right away.&lt;/p&gt;

&lt;p&gt;Want more? then you could check this &lt;a href="https://www.learn-dev-tools.blog/what-is-the-flutter-framework-a-beginners-introduction-to-flutter/" rel="noopener noreferrer"&gt;detailed article about Flutter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>opensource</category>
      <category>mobile</category>
      <category>beginners</category>
    </item>
    <item>
      <title>WordPress, Yet Another CMS</title>
      <dc:creator>Gun</dc:creator>
      <pubDate>Thu, 06 Apr 2023 14:17:06 +0000</pubDate>
      <link>https://dev.to/gunkev/wordpress-yet-another-cms-5bgh</link>
      <guid>https://dev.to/gunkev/wordpress-yet-another-cms-5bgh</guid>
      <description>&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@markuswinkler?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Markus Winkler&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/wordpress?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently talked about &lt;a href="https://dev.to/gunkev/wordpresscom-vs-wordpressorg-which-one-should-you-use-4ho0"&gt;WordPress.com and WordPress.org&lt;/a&gt; in my previous articles and actually I have been getting requests from people asking what WordPress actually is. Today you will finally feel relief cause I will give you a brief introduction to WordPress. So guys sit and get a cup of tea to savour this.&lt;br&gt;
WordPress… just another CMS&lt;/p&gt;

&lt;p&gt;WordPress is actually a free and open source Content Management System written in PHP and associated with MySQL or MariaDB databases. Initially, it was created as a blog publishing-System. But it has evolved to include different web content types (such as online stores, membership websites, forums, and media galleries…).&lt;br&gt;
CMS… What is it?&lt;/p&gt;

&lt;p&gt;Content System Management also known as CMS is simply an application that is used to manage web content. In general, it allows a contributor or contributors to create, edit, publish or destroy content(web pages, articles…). A contributor can be any person who has access to a WordPress Dashboard&lt;/p&gt;

&lt;p&gt;WordPress is equally one of the most popular content system management used today. It has also been used for other application domains such as pervasive display systems.&lt;/p&gt;

&lt;p&gt;The software was released under the GPLv2 license. Founded by an American developer Matt Mullenweg and Mike Little an English developer. WordPress is also called a factory that makes webpages. It comes along with many tools to ease management. Some of these tools include themes and plugins.&lt;/p&gt;

&lt;p&gt;Below you will find a screenshot of a section of WordPress Dashboard&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%2Fk8igv7y49ofzukd57v57.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%2Fk8igv7y49ofzukd57v57.PNG" alt="Image description" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see in the shot above, the WordPress Dashboard has many tools you can use to manage your web content. To have a better experience just install WordPress and take a look around. If you would like to set up WordPress on your own don’t worry here is a tutorial on how to install it locally.&lt;/p&gt;

&lt;p&gt;To conclude, WordPress is an application used for building web platforms. Be it a blog, a website, or a portfolio, WordPress facilitates and eases your efforts to build such platforms. For example &lt;a href="https://www.tradnow.co" rel="noopener noreferrer"&gt;MangAnime tradnow&lt;/a&gt; and &lt;a href="https://www.learn-dev-tools.blog" rel="noopener noreferrer"&gt;Learndevtools&lt;/a&gt; are two blogs which talks about anime and development build essentially with WordPress&lt;/p&gt;

&lt;p&gt;Last but not least there exist different versions of WordPress. Some of us can use WordPress.com(Practically no coding). Then developers have the chance to get beautiful documentation in order to customize and bring in some innovations with WordPress.org.&lt;/p&gt;

&lt;p&gt;I hope I was able to answer most of your questions guys. Feel free to ask more questions. I will be glad to understand and help you.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>wordpress</category>
      <category>cms</category>
    </item>
  </channel>
</rss>
