<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Be Better Dev]]></title><description><![CDATA[Developer with great amount of experience in Web App development.]]></description><link>https://blogs.aryansh.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 26 Apr 2026 21:13:59 GMT</lastBuildDate><atom:link href="https://blogs.aryansh.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[The URL: Your App's Most Underutilized State Manager]]></title><description><![CDATA[In the world of modern web development, we often reach for complex state management solutions like Redux, Zustand, or MobX without considering one of the most powerful and overlooked state managers that's been with us since the dawn of the web: the U...]]></description><link>https://blogs.aryansh.dev/url-as-state-manager</link><guid isPermaLink="true">https://blogs.aryansh.dev/url-as-state-manager</guid><category><![CDATA[url]]></category><category><![CDATA[state]]></category><category><![CDATA[State Management ]]></category><category><![CDATA[React]]></category><category><![CDATA[Angular]]></category><dc:creator><![CDATA[Aryansh Mahato]]></dc:creator><pubDate>Wed, 20 Aug 2025 10:22:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755456348325/b81bdeae-807c-4a5c-bf9d-8af923b02cd6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the world of modern web development, we often reach for complex state management solutions like Redux, Zustand, or MobX without considering one of the most powerful and overlooked state managers that's been with us since the dawn of the web: <strong>the URL itself</strong>.</p>
<p>The URL isn't just an address—it's a stateful, shareable, bookmarkable, and SEO-friendly state container that can dramatically simplify your application architecture while improving user experience.</p>
<h2 id="heading-why-to-use-url-as-a-state-manager">Why to use URL as a state manager?</h2>
<p>How many times you wanted to manage state such as filters, sorting, modal open/closed? Instead of storing it as <code>React state</code> or <code>Angular Signals</code> store it in URL as Query param, it will be easy, accessible in multiple components and most importantly, <strong>shareable</strong>.</p>
<h3 id="heading-what-do-i-mean-by-shareable">What do I mean by shareable?</h3>
<p>Suppose you're building a table with tons of filters and your users want to share those specific filtered views with their teammates. You could build some fancy "Save Filters" feature with databases and user accounts, but here's a much simpler solution: just use the damn <strong>query parameters</strong>. Store all your applied filters right in the URL like <code>/reports?status=active&amp;category=sales&amp;sortBy=revenue&amp;page=2</code>. Now when users share that link, boom—all the filters are already baked into the URL and get copied along with it. The other person clicks the link and doesn't have to mess around reapplying the same filters, and you don't have to waste time building some overcomplicated filter-saving system. Easy sharing, zero extra work. Win-win.</p>
<h2 id="heading-where-to-store-state-in-url">Where to store state in URL?</h2>
<p>There are 2 places where you can store the state, and it’s very important where you store it. Store it as <strong>Path Param</strong> or <strong>Query Param.</strong></p>
<h3 id="heading-path-parameters">Path Parameters</h3>
<p>Path parameters should be used for <strong>required values</strong> that are essential for the page to function properly. These represent core resource identifiers or mandatory data without which the page cannot render meaningful content.</p>
<p><strong>Example:</strong></p>
<pre><code class="lang-plaintext">/products/:id
</code></pre>
<p>In this route, the <code>id</code> parameter is essential for displaying product details. Without it, the application cannot determine which product to render, making the page non-functional.</p>
<h3 id="heading-query-parameters">Query Parameters</h3>
<p>Query parameters should be used for <strong>optional values</strong> that enhance or modify the default behavior of a page without preventing it from functioning. These represent filters, sorting options, pagination settings, or other supplementary configurations.</p>
<p><strong>Example:</strong></p>
<pre><code class="lang-plaintext">/products?sort=name,asc&amp;limit=20&amp;category=electronics
</code></pre>
<p>In this route, query parameters like <code>sort</code>, <code>limit</code>, and <code>category</code> are optional. The products page will render successfully without them, applying sensible defaults (e.g., default sorting, standard page size, showing all categories).</p>
<h2 id="heading-what-belongs-in-url-state">What Belongs in URL State?</h2>
<p>Not everything should live in the URL, but these types of state are perfect candidates:</p>
<ul>
<li><p><strong>Filters and search queries</strong></p>
</li>
<li><p><strong>Pagination and sorting parameters</strong></p>
</li>
<li><p><strong>Active tabs or views</strong></p>
</li>
<li><p><strong>Modal or dialog states</strong></p>
</li>
<li><p><strong>Form draft states</strong></p>
</li>
<li><p><strong>Map coordinates and zoom levels</strong></p>
</li>
<li><p><strong>Date ranges and time periods</strong></p>
</li>
</ul>
<h2 id="heading-practical-implementations">Practical Implementations</h2>
<h3 id="heading-vanilla-javascript">Vanilla JavaScript</h3>
<pre><code class="lang-javascript"><span class="hljs-comment">// Reading URL state</span>
<span class="hljs-keyword">const</span> urlParams = <span class="hljs-keyword">new</span> URLSearchParams(<span class="hljs-built_in">window</span>.location.search);
<span class="hljs-keyword">const</span> currentFilter = urlParams.get(<span class="hljs-string">'filter'</span>) || <span class="hljs-string">'all'</span>;
<span class="hljs-keyword">const</span> currentPage = <span class="hljs-built_in">parseInt</span>(urlParams.get(<span class="hljs-string">'page'</span>)) || <span class="hljs-number">1</span>;

<span class="hljs-comment">// Updating URL state</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateURLState</span>(<span class="hljs-params">key, value</span>) </span>{
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(<span class="hljs-built_in">window</span>.location);
  url.searchParams.set(key, value);
  <span class="hljs-built_in">window</span>.history.pushState({}, <span class="hljs-string">''</span>, url);
}
</code></pre>
<h3 id="heading-react-with-custom-hook">React with Custom Hook</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useURLState</span>(<span class="hljs-params">key, defaultValue</span>) </span>{
  <span class="hljs-keyword">const</span> [state, setState] = useState(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> params = <span class="hljs-keyword">new</span> URLSearchParams(<span class="hljs-built_in">window</span>.location.search);
    <span class="hljs-keyword">return</span> params.get(key) || defaultValue;
  });

  <span class="hljs-keyword">const</span> updateState = <span class="hljs-function">(<span class="hljs-params">value</span>) =&gt;</span> {
    setState(value);
    <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(<span class="hljs-built_in">window</span>.location);
    <span class="hljs-keyword">if</span> (value) {
      url.searchParams.set(key, value);
    } <span class="hljs-keyword">else</span> {
      url.searchParams.delete(key);
    }
    <span class="hljs-built_in">window</span>.history.replaceState({}, <span class="hljs-string">''</span>, url);
  };

  <span class="hljs-comment">// Listen for browser back/forward button changes</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> handlePopState = <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> params = <span class="hljs-keyword">new</span> URLSearchParams(<span class="hljs-built_in">window</span>.location.search);
      setState(params.get(key) || defaultValue);
    };

    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'popstate'</span>, handlePopState);
    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">window</span>.removeEventListener(<span class="hljs-string">'popstate'</span>, handlePopState);
  }, [key, defaultValue]);

  <span class="hljs-keyword">return</span> [state, updateState];
}

<span class="hljs-comment">// Usage example</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ProductFilter</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [category, setCategory] = useURLState(<span class="hljs-string">'category'</span>, <span class="hljs-string">'all'</span>);
  <span class="hljs-keyword">const</span> [priceRange, setPriceRange] = useURLState(<span class="hljs-string">'price'</span>, <span class="hljs-string">'0-1000'</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{category}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setCategory(e.target.value)}&gt;
        <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"all"</span>&gt;</span>All Categories<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"electronics"</span>&gt;</span>Electronics<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"clothing"</span>&gt;</span>Clothing<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<h3 id="heading-angular-with-service">Angular with Service</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;
<span class="hljs-keyword">import</span> { Router, ActivatedRoute } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/router'</span>;
<span class="hljs-keyword">import</span> { BehaviorSubject, Observable } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>;

<span class="hljs-meta">@Injectable</span>({
  providedIn: <span class="hljs-string">'root'</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> URLStateService {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">
    <span class="hljs-keyword">private</span> router: Router,
    <span class="hljs-keyword">private</span> route: ActivatedRoute
  </span>) {}

  getStateParam(key: <span class="hljs-built_in">string</span>, defaultValue: <span class="hljs-built_in">string</span> = <span class="hljs-string">''</span>): <span class="hljs-built_in">string</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.route.snapshot.queryParams[key] || defaultValue;
  }

  updateStateParam(key: <span class="hljs-built_in">string</span>, value: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">void</span> {
    <span class="hljs-keyword">const</span> queryParams = { ...this.route.snapshot.queryParams };

    <span class="hljs-keyword">if</span> (value) {
      queryParams[key] = value;
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">delete</span> queryParams[key];
    }

    <span class="hljs-built_in">this</span>.router.navigate([], {
      relativeTo: <span class="hljs-built_in">this</span>.route,
      queryParams,
      queryParamsHandling: <span class="hljs-string">'replace'</span>
    });
  }

  watchStateParam(key: <span class="hljs-built_in">string</span>, defaultValue: <span class="hljs-built_in">string</span> = <span class="hljs-string">''</span>): Observable&lt;<span class="hljs-built_in">string</span>&gt; {
    <span class="hljs-keyword">const</span> subject = <span class="hljs-keyword">new</span> BehaviorSubject&lt;<span class="hljs-built_in">string</span>&gt;(
      <span class="hljs-built_in">this</span>.getStateParam(key, defaultValue)
    );

    <span class="hljs-comment">// Subscribe to route changes</span>
    <span class="hljs-built_in">this</span>.route.queryParams.subscribe(<span class="hljs-function"><span class="hljs-params">params</span> =&gt;</span> {
      subject.next(params[key] || defaultValue);
    });

    <span class="hljs-keyword">return</span> subject.asObservable();
  }
}

<span class="hljs-comment">// Component usage example</span>
<span class="hljs-keyword">import</span> { Component, OnInit } <span class="hljs-keyword">from</span> <span class="hljs-string">'@angular/core'</span>;

<span class="hljs-meta">@Component</span>({
  selector: <span class="hljs-string">'app-product-filter'</span>,
  template: <span class="hljs-string">`
    &lt;div&gt;
      &lt;select [value]="category" (change)="onCategoryChange($event)"&gt;
        &lt;option value="all"&gt;All Categories&lt;/option&gt;
        &lt;option value="electronics"&gt;Electronics&lt;/option&gt;
        &lt;option value="clothing"&gt;Clothing&lt;/option&gt;
      &lt;/select&gt;

      &lt;input 
        [value]="searchTerm" 
        (input)="onSearchChange($event)"
        placeholder="Search products..."
      /&gt;
    &lt;/div&gt;
  `</span>
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> ProductFilterComponent <span class="hljs-keyword">implements</span> OnInit {
  category: <span class="hljs-built_in">string</span> = <span class="hljs-string">'all'</span>;
  searchTerm: <span class="hljs-built_in">string</span> = <span class="hljs-string">''</span>;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">private</span> urlStateService: URLStateService</span>) {}

  ngOnInit() {
    <span class="hljs-comment">// Initialize from URL</span>
    <span class="hljs-built_in">this</span>.category = <span class="hljs-built_in">this</span>.urlStateService.getStateParam(<span class="hljs-string">'category'</span>, <span class="hljs-string">'all'</span>);
    <span class="hljs-built_in">this</span>.searchTerm = <span class="hljs-built_in">this</span>.urlStateService.getStateParam(<span class="hljs-string">'search'</span>, <span class="hljs-string">''</span>);

    <span class="hljs-comment">// Watch for changes (back/forward navigation)</span>
    <span class="hljs-built_in">this</span>.urlStateService.watchStateParam(<span class="hljs-string">'category'</span>, <span class="hljs-string">'all'</span>)
      .subscribe(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> <span class="hljs-built_in">this</span>.category = value);

    <span class="hljs-built_in">this</span>.urlStateService.watchStateParam(<span class="hljs-string">'search'</span>, <span class="hljs-string">''</span>)
      .subscribe(<span class="hljs-function"><span class="hljs-params">value</span> =&gt;</span> <span class="hljs-built_in">this</span>.searchTerm = value);
  }

  onCategoryChange(event: Event) {
    <span class="hljs-keyword">const</span> target = event.target <span class="hljs-keyword">as</span> HTMLSelectElement;
    <span class="hljs-built_in">this</span>.category = target.value;
    <span class="hljs-built_in">this</span>.urlStateService.updateStateParam(<span class="hljs-string">'category'</span>, target.value);
  }

  onSearchChange(event: Event) {
    <span class="hljs-keyword">const</span> target = event.target <span class="hljs-keyword">as</span> HTMLInputElement;
    <span class="hljs-built_in">this</span>.searchTerm = target.value;
    <span class="hljs-built_in">this</span>.urlStateService.updateStateParam(<span class="hljs-string">'search'</span>, target.value);
  }
}
</code></pre>
<h2 id="heading-why-urls-make-excellent-state-managers">Why URLs Make Excellent State Managers</h2>
<h3 id="heading-1-inherent-persistence">1. <strong>Inherent Persistence</strong></h3>
<p>Unlike in-memory state that disappears on page refresh, URL state persists across browser sessions. Users can bookmark specific application states, share them with colleagues, or return to exactly where they left off.</p>
<h3 id="heading-2-zero-configuration-required">2. <strong>Zero Configuration Required</strong></h3>
<p>Every web application already has URL state management built-in. No additional libraries, no setup, no configuration—just leverage what's already there.</p>
<h3 id="heading-3-universal-shareability">3. <strong>Universal Shareability</strong></h3>
<p>Want to share your current filtered view of a data table? Just copy the URL. This natural shareability is something that complex state managers struggle to replicate.</p>
<h3 id="heading-4-seo-benefits">4. <strong>SEO Benefits</strong></h3>
<p>Search engines can crawl and index different URL states, making your application more discoverable. Each URL state becomes a potential landing page.</p>
<h3 id="heading-5-browser-history-integration">5. <strong>Browser History Integration</strong></h3>
<p>Users expect the back button to work. URL-based state naturally integrates with browser history, providing intuitive navigation.</p>
<h2 id="heading-real-world-examples">Real-World Examples</h2>
<h3 id="heading-e-commerce-product-filtering">E-commerce Product Filtering</h3>
<pre><code class="lang-javascript">/products?category=electronics&amp;price_min=<span class="hljs-number">100</span>&amp;price_max=<span class="hljs-number">500</span>&amp;sort=rating&amp;page=<span class="hljs-number">2</span>
</code></pre>
<p>This URL tells a complete story: showing electronics between $100-500, sorted by rating, on page 2.</p>
<h3 id="heading-analytics-dashboard">Analytics Dashboard</h3>
<pre><code class="lang-javascript">/dashboard/analytics#view=revenue&amp;timeframe=<span class="hljs-number">30</span>d&amp;breakdown=channel&amp;chart=bar
</code></pre>
<p>Every parameter represents a user choice that affects the dashboard view.</p>
<h3 id="heading-data-table-state">Data Table State</h3>
<pre><code class="lang-javascript">/users?search=john&amp;status=active&amp;sort=created_desc&amp;limit=<span class="hljs-number">50</span>&amp;offset=<span class="hljs-number">100</span>
</code></pre>
<p>The entire table state—search, filters, sorting, and pagination—lives in the URL.</p>
<h2 id="heading-best-practices">Best Practices</h2>
<h3 id="heading-1-keep-urls-human-readable">1. Keep URLs Human-Readable</h3>
<pre><code class="lang-javascript">✅ /reports?period=<span class="hljs-number">2024</span>-q1&amp;metric=revenue
❌ /reports?p=<span class="hljs-number">0x2f</span>&amp;m=rv
</code></pre>
<h3 id="heading-2-use-semantic-parameter-names">2. Use Semantic Parameter Names</h3>
<pre><code class="lang-javascript">✅ ?category=electronics&amp;sort=price_asc
❌ ?c=elec&amp;s=pa
</code></pre>
<h3 id="heading-3-provide-sensible-defaults">3. Provide Sensible Defaults</h3>
<p>Always have fallback values for missing parameters to ensure your app works with incomplete URLs.</p>
<h3 id="heading-4-validate-url-parameters">4. Validate URL Parameters</h3>
<p>Never trust URL input—validate and sanitize all parameters before using them in your application logic. If you are rendering the value in frontend then your framework should handle this, but if you are sending the param data to backend then backend should have validation to validate these things with help of zod or other validation library.</p>
<h3 id="heading-5-consider-url-length-limits">5. Consider URL Length Limits</h3>
<p>While modern browsers support very long URLs, keep them reasonable for shareability and readability.</p>
<h2 id="heading-when-not-to-use-url-state">When NOT to Use URL State</h2>
<p>URL state isn't appropriate for:</p>
<ul>
<li><p><strong>Sensitive information</strong> (passwords, API keys)</p>
</li>
<li><p><strong>Large datasets</strong> (entire table contents)</p>
</li>
<li><p><strong>Temporary UI states</strong> (loading indicators, hover states)</p>
</li>
<li><p><strong>User session data</strong> (authentication tokens)</p>
</li>
<li><p><strong>Frequently changing values</strong> (real-time counters)</p>
</li>
</ul>
<h2 id="heading-performance-considerations">Performance Considerations</h2>
<p>URL state management is generally very performant, but consider:</p>
<ul>
<li><p><strong>Debounce rapid updates</strong> to prevent history pollution</p>
</li>
<li><p><strong>Use</strong> <code>replaceState</code> vs <code>pushState</code> appropriately</p>
</li>
<li><p><strong>Limit serialized object size</strong> to keep URLs manageable</p>
</li>
<li><p><strong>Cache parsed parameters</strong> to avoid repeated parsing</p>
</li>
</ul>
<h2 id="heading-seems-like-a-silver-bullet">Seems like a silver bullet?</h2>
<p>URL as a state manager seems good right for all the scenarios? Nope! As with all the technologies here comes a catch, it’s not persistent if user loses the link. What if user have to apply same/similar filter again and again? You can’t expect user to save the link of the applied filters. If you want that users should be able to save the filters linked with their account then you’ll have to implement <code>Save Filters</code> feature.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The URL is more than just an address—it's a powerful, built-in state management solution that's been hiding in plain sight. By leveraging URLs for appropriate state, you can build applications that are more shareable, bookmarkable, and user-friendly while often reducing complexity.</p>
<p>Before reaching for that next state management library, ask yourself: "Could this live in the URL?" You might be surprised how often the answer is yes.</p>
<p>The best state manager might just be the one that's been with us all along—sitting right there in the browser's address bar, waiting to be fully utilized.</p>
]]></content:encoded></item></channel></rss>