<?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: Archit Jain</title>
    <description>The latest articles on DEV Community by Archit Jain (@0xarchit).</description>
    <link>https://dev.to/0xarchit</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%2F3147090%2F007d3c4c-8bfa-4f59-8442-87912f120d9e.jpeg</url>
      <title>DEV Community: Archit Jain</title>
      <link>https://dev.to/0xarchit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/0xarchit"/>
    <language>en</language>
    <item>
      <title>tg-logging-handler: A Complete Implementation Guide</title>
      <dc:creator>Archit Jain</dc:creator>
      <pubDate>Mon, 17 Aug 2026 08:05:58 +0000</pubDate>
      <link>https://dev.to/0xarchit/tg-logging-handler-a-complete-implementation-guide-2c74</link>
      <guid>https://dev.to/0xarchit/tg-logging-handler-a-complete-implementation-guide-2c74</guid>
      <description>&lt;p&gt;&lt;code&gt;tg-logging-handler&lt;/code&gt; is a &lt;code&gt;logging.Handler&lt;/code&gt; that delivers Python log records to a Telegram chat through the Bot API. It ships the pieces that always end up hand-rolled in a throwing script: a worker thread that never blocks your code, batching, exponential-backoff retries, 429 rate-limit handling, oversized-message policies, &lt;code&gt;parse_mode&lt;/code&gt; escaping, and a bounded queue with counted drops.&lt;/p&gt;

&lt;p&gt;Version 0.1.4, MIT license, Python 3.10 and newer. The only runtime dependency is &lt;code&gt;httpx&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Package page: &lt;a href="https://pypi.org/project/tg-logging-handler/" rel="noopener noreferrer"&gt;pypi.org/project/tg-logging-handler&lt;/a&gt;&lt;br&gt;
source: &lt;a href="https://github.com/0xarchit/tg-logging-handler" rel="noopener noreferrer"&gt;github.com/0xarchit/tg-logging-handler&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;tg-logging-handler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Everything below runs offline against mocks unless stated; the live path needs a real bot token.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup and environment
&lt;/h2&gt;

&lt;p&gt;Create a bot with &lt;a href="https://t.me/BotFather" rel="noopener noreferrer"&gt;@BotFather&lt;/a&gt; and find your chat id with @getidsbot. Put the credentials in the environment, which is also how you avoid hardcoding secrets:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TG_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TG_CHAT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nt"&gt;-100123456789&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler resolves three things in the same way, argument first, environment second:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Argument&lt;/th&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bot token&lt;/td&gt;
&lt;td&gt;&lt;code&gt;token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TG_TOKEN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required; &lt;code&gt;TelegramConfigError&lt;/code&gt; if missing or malformed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target chat&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chat_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TG_CHAT_ID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required; groups may use a negative id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Topic&lt;/td&gt;
&lt;td&gt;&lt;code&gt;topic_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TG_TOPIC_ID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional, see the forum topics section&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The minimal example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tg_logging_handler&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TelegramLoggingHandler&lt;/span&gt;

&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TelegramLoggingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker started&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed to connect to db: connection refused&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire integration. A worker thread starts at construction, &lt;code&gt;emit()&lt;/code&gt; never blocks or raises, and &lt;code&gt;close()&lt;/code&gt; is registered with &lt;code&gt;atexit&lt;/code&gt;, so shutdown is covered too. &lt;code&gt;TGLoggingHandler&lt;/code&gt; is a shorter alias for the same class.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Every &lt;code&gt;emit()&lt;/code&gt; call copies the &lt;code&gt;LogRecord&lt;/code&gt; (records can be mutated or recycled by frameworks, so the handler snapshots them first) and merges the message args immediately. The snapshot goes into a bounded &lt;code&gt;queue.Queue&lt;/code&gt;. A single daemon thread, owned by the handler instance, runs the rest of the pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;BatchAccumulator.collect()&lt;/code&gt; drains the queue until &lt;code&gt;batch_size&lt;/code&gt; records or &lt;code&gt;flush_interval&lt;/code&gt; elapses.&lt;/li&gt;
&lt;li&gt;Each record is formatted and escaped for the configured &lt;code&gt;parse_mode&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prepare_messages()&lt;/code&gt; applies the overflow policy to the joined text.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TelegramSender.send_with_retry()&lt;/code&gt; POSTs each message to &lt;code&gt;/bot&amp;lt;token&amp;gt;/sendMessage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Outcomes are counted in &lt;code&gt;handler.stats&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything network-bound lives on the worker thread. The application thread only ever touches a queue, which is why a slow or dead Bot API cannot stall a request handler. If the worker's formatting or sending ever raises internally, the error goes to &lt;code&gt;stderr&lt;/code&gt; (never back through &lt;code&gt;logging&lt;/code&gt;, so there is no recursion loop), the batch is counted, and the loop continues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batching
&lt;/h2&gt;

&lt;p&gt;The default &lt;code&gt;batch_size=1&lt;/code&gt; sends each record as its own message, which is correct for alerts and wasteful for high-volume services. Batching cuts message count and rate-limit pressure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TelegramLoggingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;# up to 10 records per message
&lt;/span&gt;    &lt;span class="n"&gt;flush_interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# ...or flush a partial batch after 10 seconds
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A batch is sent when &lt;code&gt;batch_size&lt;/code&gt; records accumulate or when &lt;code&gt;flush_interval&lt;/code&gt; seconds pass since the first queued record, whichever comes first. The trade-off is visibility latency: a partial batch sits for up to &lt;code&gt;flush_interval&lt;/code&gt; seconds. The flush timer only runs while the queue is non-empty; a quiet handler does not busy-poll.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retries and rate limits
&lt;/h2&gt;

&lt;p&gt;Transient failures (network errors and 5xx responses) are retried with exponential backoff: 0.5 seconds initially, doubling per attempt, with ±25% jitter, capped at 30 seconds. &lt;code&gt;max_retries=3&lt;/code&gt; is the default, and retries consume that budget.&lt;/p&gt;

&lt;p&gt;429 responses are a separate path, because they carry a &lt;code&gt;Retry-After&lt;/code&gt; header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TelegramLoggingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# more budget for flaky networks
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The handler sleeps the server-provided &lt;code&gt;Retry-After&lt;/code&gt; (clamped to 30 seconds, malformed values fall back to 1 second) and retries, without spending the retry budget.&lt;/li&gt;
&lt;li&gt;Every 429 wait is counted in &lt;code&gt;stats.rate_limited&lt;/code&gt;, so a rate-limit storm shows up in the stats even at zero retries (429s never consume the retry budget).&lt;/li&gt;
&lt;li&gt;A non-numeric or infinite &lt;code&gt;Retry-After&lt;/code&gt; (some servers send HTTP dates) is normalized instead of crashing the sleep.&lt;/li&gt;
&lt;li&gt;Consecutive 429 waits are capped at 10, so a stuck 429 cannot spin the worker forever. Beyond the cap the batch is dropped, counted as &lt;code&gt;failed&lt;/code&gt;, and reported to &lt;code&gt;stderr&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The first 429 of a sender's lifetime posts a one-time heads-up message into the chat (in the same topic as your logs, see below). It is sent only once, best-effort, and its own failure is swallowed.&lt;/li&gt;
&lt;li&gt;HTTP redirects are never followed: a 302/308 with a &lt;code&gt;Location&lt;/code&gt; header (or any non-200 response) surfaces as a permanent failure, so a redirected request can never be mistaken for a delivery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Telegram stays rate-limited longer than the cap window, you lose messages. &lt;code&gt;stats.failed&lt;/code&gt; is the ground truth for that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 4096-character cap and overflow policies
&lt;/h2&gt;

&lt;p&gt;Telegram rejects a &lt;code&gt;sendMessage&lt;/code&gt; payload longer than 4096 characters. Tracebacks alone regularly exceed this in a batch, so the handler applies an &lt;code&gt;overflow&lt;/code&gt; policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TelegramLoggingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;overflow&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;split&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# default
&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TelegramLoggingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;overflow&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;truncate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# one message, cut
&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TelegramLoggingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;overflow&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# nothing sent
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;split&lt;/code&gt; cuts the text into numbered parts, &lt;code&gt;(1/3)&lt;/code&gt;, &lt;code&gt;(2/3)&lt;/code&gt;, &lt;code&gt;(3/3)&lt;/code&gt;, that reconstruct the original exactly when pasted back together. The split is &lt;code&gt;parse_mode&lt;/code&gt;-aware: it never breaks a MarkdownV2 escape pair across parts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;truncate&lt;/code&gt; sends a single message cut to the cap with &lt;code&gt;… [truncated]&lt;/code&gt; appended. The marker is clamped when the cap is smaller than the marker itself, and nudged off a trailing backslash, so a tiny &lt;code&gt;max_length&lt;/code&gt; (and MarkdownV2 escaping) can never produce an oversized payload or a dangling escape pair.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;drop&lt;/code&gt; sends nothing; the records are counted as &lt;code&gt;dropped&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The accounting here is deliberate and worth knowing: a &lt;code&gt;drop&lt;/code&gt; batch increments &lt;code&gt;dropped&lt;/code&gt; by the batch size, while any send path that fails to deliver (including one failed part of a split batch) increments &lt;code&gt;failed&lt;/code&gt; by the batch size. A split batch counts as &lt;code&gt;sent&lt;/code&gt; only when every part is delivered, never a partial success.&lt;/p&gt;

&lt;h2&gt;
  
  
  parse_mode escaping
&lt;/h2&gt;

&lt;p&gt;Telegram offers Markdown, MarkdownV2, and HTML formatting, and every parser has characters that will either 400 the request or render wrong when they appear literally in log content. A user-supplied string with an underscore or asterisk is normal in logs. The handler escapes the entire formatted record for the configured mode before anything goes on the wire:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TelegramLoggingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parse_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MarkdownV2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version mismatch: v1.2_final vs v1.3_rc1&lt;/span&gt;&lt;span class="sh"&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;v1.2_final&lt;/code&gt; arrives as literal text. The truncation marker and split headers are added after escaping, so they render correctly too. With &lt;code&gt;parse_mode=None&lt;/code&gt; nothing is escaped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forum topics
&lt;/h2&gt;

&lt;p&gt;Groups with topics enabled, and forum supergroups, can split a chat into threads. One handler instance targets exactly one topic, and the topic is configurable per deployment via the &lt;code&gt;TG_TOPIC_ID&lt;/code&gt; environment variable instead of code:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TG_TOPIC_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TelegramLoggingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# same effect as the env var
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Implementation details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The handler sends the official Bot API &lt;code&gt;message_thread_id&lt;/code&gt; field; &lt;code&gt;topic_id&lt;/code&gt; is only the user-facing name. The value has to be a positive integer; &lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, or a string rejected at construction with a &lt;code&gt;ValueError&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The explicit argument wins over &lt;code&gt;TG_TOPIC_ID&lt;/code&gt;. Invalid values (a non-numeric env var, zero or negative ids) fail fast at construction, not at the first send.&lt;/li&gt;
&lt;li&gt;When neither is set, the payload does not carry the field at all, which is byte-identical to older versions: messages go to the group directly, or to the General topic when the group has topics enabled.&lt;/li&gt;
&lt;li&gt;The one-time 429 heads-up notice is posted into the same topic, so an operator watching the topic sees the notice instead of a phantom message in General.&lt;/li&gt;
&lt;li&gt;A wrong or closed topic id comes back as &lt;code&gt;400 Bad Request: message thread not found&lt;/code&gt;. That is a permanent failure: no retries, the batch counts as &lt;code&gt;failed&lt;/code&gt;, and it shows in the stats.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sharp edge: omitting the id does not error, it silently targets General. If you need a specific topic, set the id explicitly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bounded queue and backpressure
&lt;/h2&gt;

&lt;p&gt;The queue defaults to 10,000 records. When it is full, &lt;code&gt;queue_full_policy&lt;/code&gt; decides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;block&lt;/code&gt;: &lt;code&gt;emit()&lt;/code&gt; waits for space. The only policy that can stall your application.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;drop_newest&lt;/code&gt; (default): the incoming record is discarded.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;drop_oldest&lt;/code&gt;: the oldest queued record is evicted to make room, best-effort under concurrency, with every victim counted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every dropped record increments the &lt;code&gt;dropped&lt;/code&gt; counter, so losses are visible. For a logging path, &lt;code&gt;drop_newest&lt;/code&gt; is usually right: losing the newest line under a flood beats hanging the request handler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stats
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;handler.stats&lt;/code&gt; returns an immutable snapshot of seven cumulative counters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queued&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;batches_sent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rate_limited&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dropped&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;queued&lt;/code&gt;: records accepted by &lt;code&gt;emit&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sent&lt;/code&gt;: records Telegram accepted, one batch at a time.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;batches_sent&lt;/code&gt;: successful &lt;code&gt;sendMessage&lt;/code&gt; rounds.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;retries&lt;/code&gt;: transient retries performed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rate_limited&lt;/code&gt;: 429 &lt;code&gt;Retry-After&lt;/code&gt; waits honored (distinct from &lt;code&gt;retries&lt;/code&gt;, which 429s never consume).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;failed&lt;/code&gt;: records dropped after send failure, or one per batch when formatting raised.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dropped&lt;/code&gt;: records discarded by overflow="drop" or queue-full policies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two failure counters do not mean the same thing. &lt;code&gt;dropped&lt;/code&gt; is by design (a policy choice), &lt;code&gt;failed&lt;/code&gt; is delivery breakdown. The split-batch rule from the overflow section applies: failed parts count the whole batch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Construction-time validation
&lt;/h2&gt;

&lt;p&gt;Everything that can fail early fails at construction, because a typo discovered at 3am in a log pipeline is expensive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing or malformed credentials raise &lt;code&gt;TelegramConfigError&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Env values (&lt;code&gt;TG_TOKEN&lt;/code&gt;, &lt;code&gt;TG_CHAT_ID&lt;/code&gt;) are stripped of trailing whitespace, so credentials read from &lt;code&gt;.env&lt;/code&gt; files or secrets managers with a stray newline validate cleanly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;batch_size &amp;lt; 1&lt;/code&gt;, negative &lt;code&gt;flush_interval&lt;/code&gt;, &lt;code&gt;max_retries &amp;lt; 0&lt;/code&gt;, &lt;code&gt;queue_maxsize &amp;lt; 0&lt;/code&gt; raise &lt;code&gt;ValueError&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;An unknown &lt;code&gt;queue_full_policy&lt;/code&gt; name raises &lt;code&gt;ValueError&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A non-integer or non-positive &lt;code&gt;topic_id&lt;/code&gt; raises &lt;code&gt;ValueError&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;With &lt;code&gt;validate=True&lt;/code&gt; (default), a synchronous &lt;code&gt;getMe&lt;/code&gt; call verifies the token right away; failures raise &lt;code&gt;TelegramConfigError&lt;/code&gt;. The response must be an object with &lt;code&gt;ok == True&lt;/code&gt; exactly: a scalar/list JSON body or a merely truthy &lt;code&gt;ok&lt;/code&gt; maps to &lt;code&gt;TelegramConfigError&lt;/code&gt; too, never a raw &lt;code&gt;AttributeError&lt;/code&gt; or a false accept. Set &lt;code&gt;validate=False&lt;/code&gt; in tests and offline environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the only exceptions you can catch, and only during construction: &lt;code&gt;emit()&lt;/code&gt; swallows everything per the stdlib contract, and send failures are counted and reported, never raised into your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  dictConfig
&lt;/h2&gt;

&lt;p&gt;Framework config files work through standard &lt;code&gt;logging.config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging.config&lt;/span&gt;

&lt;span class="n"&gt;LOGGING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;handlers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telegram&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;()&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tg_logging_handler.TelegramLoggingHandler&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERROR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;batch_size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;topic_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;validate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;root&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;handlers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telegram&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WARNING&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dictConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOGGING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Shutdown
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;close()&lt;/code&gt; is idempotent and registered with &lt;code&gt;atexit&lt;/code&gt;, so it runs at interpreter exit too. It is also race-proof in both directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The shutdown signal is an internal &lt;code&gt;threading.Event&lt;/code&gt;, with the queue sentinel as only a wakeup hint; a saturated queue or a &lt;code&gt;drop_oldest&lt;/code&gt; eviction can never lose the shutdown and leak the worker thread.&lt;/li&gt;
&lt;li&gt;The sentinel goes onto the queue &lt;em&gt;before&lt;/em&gt; the event is set, so the worker can never exit while a sentinel is still about to be enqueued (which would hang any user-side &lt;code&gt;queue.join()&lt;/code&gt; forever).&lt;/li&gt;
&lt;li&gt;The worker polls the event between capped waits, so shutdown latency is bounded to about a second even with a partial batch pending and a long &lt;code&gt;flush_interval&lt;/code&gt;. The pending batch is still flushed first.&lt;/li&gt;
&lt;li&gt;A shutdown that arrives mid-drain never splits a batch: records that keep flowing still group into one batch, so a burst doesn't flush as N single-record messages.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;close()&lt;/code&gt; waits up to &lt;code&gt;shutdown_timeout&lt;/code&gt; (default 5 seconds) for the worker; anything still in flight afterwards is finished by the daemon worker in the background, so in a short script call &lt;code&gt;close()&lt;/code&gt; explicitly, and read &lt;code&gt;handler.stats&lt;/code&gt; after the worker has actually finished, not immediately after &lt;code&gt;close()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try these next
&lt;/h2&gt;

&lt;p&gt;Small things worth experimenting with, roughly in the order you should try them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;max_retries=0&lt;/code&gt; to see the failure path fast, then watch &lt;code&gt;stats.failed&lt;/code&gt; and the &lt;code&gt;stderr&lt;/code&gt; report during an outage.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;batch_size=50&lt;/code&gt; with a fake slow network (the test suite uses respx mocks) and watch &lt;code&gt;batches_sent&lt;/code&gt; stay low while &lt;code&gt;sent&lt;/code&gt; climbs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;overflow="drop"&lt;/code&gt; on a batch with a giant traceback, then compare &lt;code&gt;dropped&lt;/code&gt; to &lt;code&gt;failed&lt;/code&gt; in the stats.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;queue_full_policy="block"&lt;/code&gt; with a tiny &lt;code&gt;queue_maxsize=2&lt;/code&gt; to feel the backpressure your app inherits, then go back to &lt;code&gt;drop_newest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;parse_mode="HTML"&lt;/code&gt; on a message that contains raw &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; tags from user data.&lt;/li&gt;
&lt;li&gt;A second &lt;code&gt;TG_TOPIC_ID&lt;/code&gt; deployment pointed at a different topic of the same group, to split alerts by environment without code changes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;api_base_url&lt;/code&gt; pointed at a self-hosted Bot API server, together with &lt;code&gt;validate=False&lt;/code&gt; for a fully offline test rig.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;handler.stats&lt;/code&gt; before, during, and after a burst, to see the accounting rules from the overflow section in action.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this fits
&lt;/h2&gt;

&lt;p&gt;A Telegram chat is not searchable storage, so use this for alerting and notifications, not log archiving. It coexists fine with an error-tracking service: critical errors page, everything else floods Telegram. Keep the token in the environment, set &lt;code&gt;validate=True&lt;/code&gt; in production, and watch &lt;code&gt;failed&lt;/code&gt; plus &lt;code&gt;dropped&lt;/code&gt; rather than assuming delivery.&lt;/p&gt;

&lt;p&gt;The suite behind this package runs offline with mocks, enforces type checking and linting, and gates on coverage; the published wheel ships &lt;code&gt;py.typed&lt;/code&gt; markers, so type checkers see the full API.&lt;/p&gt;

</description>
      <category>python</category>
      <category>logging</category>
      <category>tutorial</category>
      <category>indidev</category>
    </item>
    <item>
      <title>Surge Usage Documentation | A Powerful CLI Tool for Effortless Frontend Deployments</title>
      <dc:creator>Archit Jain</dc:creator>
      <pubDate>Fri, 16 May 2025 11:45:59 +0000</pubDate>
      <link>https://dev.to/0xarchit/surge-usage-documentation-a-powerful-cli-tool-for-effortless-frontend-deploymentc-2nmn</link>
      <guid>https://dev.to/0xarchit/surge-usage-documentation-a-powerful-cli-tool-for-effortless-frontend-deploymentc-2nmn</guid>
      <description>&lt;p&gt;Hello, I'm Archit, and today I'm presenting a comprehensive guide on Surge—a tool I rely on extensively for its effortless, zero-configuration deployments. This article isn’t written arbitrarily; it encapsulates my practical experiences and insights, making it an invaluable resource for new users. I aim to help you quickly master everything from installing the Surge CLI and hosting basic sites to automating deployments with GitHub Actions and configuring custom domains. Enjoy the read and happy deploying!&lt;/p&gt;

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

&lt;p&gt;Surge is a command-line tool designed for frictionless, zero-configuration publishing of static websites and single-page applications. In just a few seconds, you can deploy your project to Surge's global CDN, take advantage of free SSL/TLS provisioning for &lt;code&gt;surge.sh&lt;/code&gt; subdomains, custom domain support, caching controls, and advanced features such as CORS configuration, redirects, and access management (with Surge Plus).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This guide covers:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Installing Surge CLI&lt;/li&gt;
&lt;li&gt;Basic Hosting with Surge&lt;/li&gt;
&lt;li&gt;Hosting a React Web App with Surge&lt;/li&gt;
&lt;li&gt;GitHub Actions Workflow for Auto-Deploy React App&lt;/li&gt;
&lt;li&gt;Surge with Custom Domain&lt;/li&gt;
&lt;li&gt;Advanced Features&lt;/li&gt;
&lt;li&gt;Why Surge?&lt;/li&gt;
&lt;li&gt;Troubleshooting&lt;/li&gt;
&lt;li&gt;Best Practices&lt;/li&gt;
&lt;li&gt;Credits&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  01. Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js (v14+) and npm installed.&lt;/li&gt;
&lt;li&gt;A Surge account; sign up at &lt;a href="https://surge.sh" rel="noopener noreferrer"&gt;https://surge.sh&lt;/a&gt; via cli.&lt;/li&gt;
&lt;li&gt;(Optional) Cloudflare account for free SSL on custom domains.&lt;/li&gt;
&lt;li&gt;I'm using npm throughout this guide, but you can opt for your preferred package manager if needed.&lt;/li&gt;
&lt;li&gt;I'm considering &lt;code&gt;./dist&lt;/code&gt; as the output path for Vite. Feel free to adjust it as needed.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  02. Installing Surge CLI
&lt;/h2&gt;

&lt;p&gt;First, install Surge globally using npm or Yarn:&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; &lt;span class="nt"&gt;--global&lt;/span&gt; surge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installation, verify by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surge &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  03. Basic Hosting with Surge
&lt;/h2&gt;

&lt;p&gt;i. &lt;strong&gt;Build or prepare your static site&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ensure your static files (HTML, CSS, JS) are in a local folder, e.g., &lt;code&gt;public/&lt;/code&gt; or &lt;code&gt;dist/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;ii. &lt;strong&gt;Login to Surge&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   surge login
   &lt;span class="c"&gt;# Enter your email and password when prompted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;iii. &lt;strong&gt;Publish your site&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   surge ./dist your-subdomain.surge.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;./dist&lt;/code&gt; is the local folder containing static assets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;your-subdomain.surge.sh&lt;/code&gt; is your chosen subdomain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;iv. &lt;strong&gt;Update or remove deployments&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To update, run the same &lt;code&gt;surge&lt;/code&gt; command with updated folder contents.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To remove, use the CLI command:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; surge teardown your-subdomain.surge.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GitHub Actions Deployment for Static Sites
&lt;/h3&gt;

&lt;p&gt;Automate your HTML/CSS/JS site deployment with this workflow. Adjust &lt;code&gt;project&lt;/code&gt; if your assets live elsewhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Static Site to Surge&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Surge CLI&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install --global surge&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Surge&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SURGE_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SURGE_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;surge --project ./ --domain &amp;lt;your-prefered-surge-subdomain&amp;gt;.surge.sh --token $SURGE_TOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Change path and domain accordingly*&lt;br&gt;
For custom domain (dns updated) use&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;surge &lt;span class="nt"&gt;--project&lt;/span&gt; ./ &lt;span class="nt"&gt;--domain&lt;/span&gt; xyz.com &lt;span class="nt"&gt;--token&lt;/span&gt; &lt;span class="nv"&gt;$SURGE_TOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  04. Hosting a React Web App with Surge
&lt;/h2&gt;

&lt;p&gt;React apps built with Create React App output static files ready for Surge.&lt;/p&gt;

&lt;p&gt;i. &lt;strong&gt;Create a production build&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates a &lt;code&gt;dist/&lt;/code&gt; folder with optimized production assets.&lt;/p&gt;

&lt;p&gt;ii. &lt;strong&gt;Deploy to Surge&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   surge ./dist my-react-app.surge.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pro Tip:&lt;/strong&gt; Add a &lt;code&gt;deploy&lt;/code&gt; script to your &lt;code&gt;package.json&lt;/code&gt; to simplify deployment:&lt;/p&gt;


&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;package.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;scripts...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deploy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run build &amp;amp;&amp;amp; surge ./dist my-react-app.surge.sh"&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;p&gt;Now you can deploy with:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;iii. &lt;strong&gt;Enable SPA routing (single-page application)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a &lt;code&gt;200.html&lt;/code&gt; fallback so all client-side routes resolve:&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="nb"&gt;cd &lt;/span&gt;dist
   &lt;span class="nb"&gt;cp &lt;/span&gt;index.html 200.html
   surge &lt;span class="nb"&gt;.&lt;/span&gt; my-react-app.surge.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  05. GitHub Actions Workflow for Auto-Deploy React App
&lt;/h2&gt;

&lt;p&gt;Automate your deployments on every push to the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;.github/workflows/deploy-react-surge.yml&lt;/code&gt; in your repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy React App to Surge&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Deploy&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Node.js&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;18'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Copy fallback for SPA&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cd dist&lt;/span&gt;
          &lt;span class="s"&gt;cp index.html 200.html&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Surge&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SURGE_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SURGE_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install --global surge&lt;/span&gt;
          &lt;span class="s"&gt;surge --project ./dist --domain &amp;lt;your-prefered-surge-subdomain&amp;gt;.surge.sh --token $SURGE_TOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Getting your Surge token
&lt;/h3&gt;

&lt;p&gt;First, you’ll need your token from the Surge CLI. This secret key lets services like GitHub Actions authenticate and publish projects on your behalf. Get your Surge token by running the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;surge token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll be asked to log in again, and afterwards your token will be displayed like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;token: ar892djio57hd......
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, add this token as a secret named &lt;code&gt;SURGE_TOKEN&lt;/code&gt; in your GitHub repository settings.&lt;/p&gt;




&lt;h2&gt;
  
  
  06. Surge with Custom Domain
&lt;/h2&gt;

&lt;p&gt;To use a custom domain with Surge:&lt;/p&gt;

&lt;p&gt;i. &lt;strong&gt;Add your domain to the project&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   surge ./dist your-domain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ii. &lt;strong&gt;Configure DNS&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a CNAME record for your domain (e.g., &lt;code&gt;www&lt;/code&gt; or &lt;code&gt;@&lt;/code&gt;) pointing to &lt;code&gt;geo.surge.world&lt;/code&gt; (or other appropriate Surge endpoint but this is new and it automatically routes traffic to the most optimal Surge datacenter based on location).&lt;/li&gt;
&lt;li&gt;If using an apex/naked domain, add an ALIAS/ANAME record pointing to &lt;code&gt;45.55.110.124&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;iii. &lt;strong&gt;Verify SSL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Surge provides automatic SSL/TLS via Let’s Encrypt for &lt;code&gt;surge.sh&lt;/code&gt; subdomains. For custom domains, SSL requires Surge Plus or a third-party service like Cloudflare. It may take a few minutes for certificates to be issued with Surge Plus.&lt;/p&gt;

&lt;p&gt;iv. &lt;strong&gt;Free SSL with Cloudflare&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Surge's free plan does not include SSL for custom domains. However, you can use Cloudflare's free tier to enable HTTPS:&lt;/p&gt;

&lt;p&gt;a. Create a Cloudflare account and add your domain.&lt;br&gt;
   b. Update your domain's nameservers to the ones provided by Cloudflare.&lt;br&gt;
   c. In the Cloudflare dashboard, navigate to &lt;strong&gt;SSL/TLS&lt;/strong&gt; settings and set the encryption mode to &lt;strong&gt;Flexible&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.amazonaws.com%2Fuploads%2Farticles%2Fgkqege2q2r8xz8kwraac.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%2Fgkqege2q2r8xz8kwraac.png" alt="Cloudflare dashboard ssl config screenshot" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;d. Ensure your DNS records (CNAME or ALIAS) point to your Surge endpoint (e.g., &lt;code&gt;geo.surge.world&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;With Flexible mode, Cloudflare will serve HTTPS to your visitors while connecting to Surge over HTTP, providing free SSL encryption.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cloudflare ssl setup can take anywhere between 15 minutes to 24 hours.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;v. &lt;strong&gt;Managing multiple domains&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can deploy the same folder to multiple domains in 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;   surge ./dist www.example.com example.net hello.example.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  07. Advanced Features
&lt;/h2&gt;

&lt;p&gt;Advanced features like CORS, redirects, and password protection require Surge Plus.&lt;/p&gt;

&lt;p&gt;i. &lt;strong&gt;CORS Configuration&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Add a &lt;code&gt;CORS&lt;/code&gt; file in your project root to control cross-origin requests (requires Surge Plus). For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Access-Control-Allow-Origin: *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ii. &lt;strong&gt;Redirects and Rewrites&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Use a &lt;code&gt;ROUTER&lt;/code&gt; file to manage redirects and clean URLs. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   301 /old-path /new-path
   301 /contact /about
   200 /api/* https://api.example.com/:splat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;iii. &lt;strong&gt;Access Control&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Password-protect your site with an &lt;code&gt;AUTH&lt;/code&gt; file (requires Surge Plus):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   username:password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;iv. &lt;strong&gt;Ignore File and Directories&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Just like &lt;code&gt;.gitignore&lt;/code&gt;, if you want to exclude specific files or directories from deployment, create a &lt;code&gt;.surgeignore&lt;/code&gt; file and list them inside. &lt;br&gt;
To override something on the default ignore list or make it must include, add it to your own &lt;code&gt;.surgeignore&lt;/code&gt; file with an &lt;strong&gt;exclamation mark&lt;/strong&gt; (&lt;code&gt;!&lt;/code&gt;) in front.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;.git
.vscode
&lt;span class="o"&gt;!&lt;/span&gt;src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;v. &lt;strong&gt;Version Management&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Surge does not support direct rollbacks. To revert to a previous version, manually redeploy the desired build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   surge ./previous-build my-site.surge.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To remove a deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   surge teardown my-site.surge.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  08. Why To Choose Surge?
&lt;/h2&gt;

&lt;p&gt;Surge shines in specific use cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplicity &amp;amp; Speed&lt;/strong&gt;: Surge excels at pure simplicity and speed with its zero-configuration approach and lightning-fast deployments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CLI-Native Workflow&lt;/strong&gt;: If you prefer command-line workflows over GUI interfaces and git-based deployments, Surge offers the most streamlined experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quick Prototyping&lt;/strong&gt;: For rapid prototyping and sharing static builds, nothing beats the speed and ease of &lt;code&gt;surge ./folder my-project.surge.sh&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low Overhead&lt;/strong&gt;: No need for repository setup, build configurations, or web interfaces - just deploy directly from any directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multiple Domain Support&lt;/strong&gt;: The ability to deploy to multiple domains in a single command is uniquely powerful.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Surge is ideal when you need the fastest path from local files to live website with minimal configuration. While platforms like Netlify, vercel and Cloudflare Pages offer more features (continuous integration, server-side functions, etc.), Surge's focused approach makes it the perfect tool for developers who value simplicity and speed.&lt;/p&gt;




&lt;h2&gt;
  
  
  09. Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication errors&lt;/strong&gt;: Run &lt;code&gt;surge logout &amp;amp;&amp;amp; surge login&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS propagation delays&lt;/strong&gt;: Wait up to 24 hours; verify using &lt;code&gt;dig&lt;/code&gt; or &lt;code&gt;nslookup&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSL issues&lt;/strong&gt;: Check case-sensitive CNAME records and ensure Cloudflare's SSL mode is set correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build failures&lt;/strong&gt;: Confirm local build works (&lt;code&gt;npm run build&lt;/code&gt;) before deploying.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  10. Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Keep your &lt;code&gt;build&lt;/code&gt; or &lt;code&gt;dist&lt;/code&gt; directory clean and up to date.&lt;/li&gt;
&lt;li&gt;Version control your Surge configuration files (&lt;code&gt;CORS&lt;/code&gt;, &lt;code&gt;ROUTER&lt;/code&gt;, &lt;code&gt;AUTH&lt;/code&gt;) in the project root.&lt;/li&gt;
&lt;li&gt;Automate deployments on pull request previews using branches and surge subdomains.&lt;/li&gt;
&lt;li&gt;Monitor performance using Lighthouse or a CDN analytics tool.&lt;/li&gt;
&lt;li&gt;Regularly audit and update security headers.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  11. Credits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;My personal experience in using Surge&lt;/li&gt;
&lt;li&gt;Official Surge.sh documentation: &lt;a href="https://surge.sh/help/" rel="noopener noreferrer"&gt;https://surge.sh/help/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DNS configuration insights from &lt;a href="https://geo.surge.world/" rel="noopener noreferrer"&gt;https://geo.surge.world/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Additional insights from various articles on Medium and Dev.to&lt;/li&gt;
&lt;li&gt;ChatGPT for Markdown formatting, language improvements, and fixations&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Happy deploying with Surge!&lt;/p&gt;

&lt;p&gt;Authored By: &lt;a href="https://0xarchit.carrd.co" rel="noopener noreferrer"&gt;0xArchit&lt;/a&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>staticwebapps</category>
      <category>opensource</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
