<?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</title>
    <description>The most recent home feed on DEV Community.</description>
    <link>https://dev.to</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed"/>
    <language>en</language>
    <item>
      <title>How I got 165 blog posts indexed in 30 days with zero backlinks</title>
      <dc:creator>CarsonJ</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:19:42 +0000</pubDate>
      <link>https://dev.to/korelyy/how-i-got-165-blog-posts-indexed-in-30-days-with-zero-backlinks-1dob</link>
      <guid>https://dev.to/korelyy/how-i-got-165-blog-posts-indexed-in-30-days-with-zero-backlinks-1dob</guid>
      <description>&lt;p&gt;Most SEO guides assume you have a domain authority of 30+, a backlink profile, or a budget for links. I had none of those.\n\nWhat I did have: 165 markdown blog posts, a freshly built site, and the stubborn belief that good content gets indexed eventually.\n\nHere is the exact playbook. No paid tools, no shady link schemes.\n\n## The setup\n\n- Brand new domain (3 months old at the time)\n- Next.js + Cloudflare Pages\n- 6 languages via next-intl\n- 109 free browser-based tools (the actual product)\n- 165 blog posts translated into 6 languages (~990 pages total)\n- Zero backlinks (I was starting from scratch)\n- Zero marketing budget\n\n## Step 1: Make sure Google can find you\n\nBefore any content, you need three things working:\n\n*&lt;em&gt;robots.txt&lt;/em&gt;* - explicit allow, no surprises\n*&lt;em&gt;sitemap.xml&lt;/em&gt;* - one entry per public URL, lastmod set correctly\n*&lt;em&gt;Search Console&lt;/em&gt;* - submit the sitemap, watch for errors\n\nI lost 3 weeks to a trailing slash mismatch (en/tools vs en/tools/). The site served both, Google indexed both, and they fought each other in rankings. Fix: pick one canonical form and 308 redirect the other. Use hreflang tags for languages.\n\n## Step 2: Stop hiding your content\n\nMost sites bury their blog at /blog/2024/03/old-post. Google has to crawl 3 levels deep. Put everything 1-2 clicks from the homepage.\n\nMy structure:\n- /[lang]/tools/&lt;a href="https://dev.to109%20of%20these"&gt;tool&lt;/a&gt;\n- /[lang]/blog/&lt;a href="https://dev.to165%20of%20these"&gt;post&lt;/a&gt;\n- /[lang]/workflows (288 work flows)\n\nAll in sitemap, all 1 click from home, all interlinked.\n\n## Step 3: Internal links beat external links\n\nI have zero backlinks. But I have hundreds of internal links.\n\nEvery blog post links to 3-5 related tools. Every tool page links to 2-3 related blog posts. Every category page links to all sub-pages.\n\nThis does two things:\n1. Distributes page rank. A high-traffic blog post can pass authority to tool pages.\n2. Helps Google discover pages faster. New content gets crawled within hours, not weeks.\n\nAfter 30 days: 612/736 pages indexed (83%). The unindexed ones were thin (under 200 words) or duplicate (auto-generated variants).\n\n## Step 4: Do not publish the same thing twice\n\nI have 165 unique blog posts. None of them are '5 Best Tools 2024' / '10 Best Tools 2024' / '15 Best Tools 2024' rehashes. Each post is one specific topic, one specific angle, ~1500 words.\n\nIf I have to say the same thing, I update the old post. I do not write a new one.\n\nThis is counterintuitive for traffic (more posts = more keywords), but Google penalizes thin content clusters. 5 great posts rank better than 50 mediocre ones.\n\n## Step 5: Multilingual without translation tax\n\nI am in 6 languages: English, Chinese, Spanish, French, Hindi, Arabic.\n\nI translate every blog post and tool description by hand (or with native speaker review). No machine-translated filler.\n\nKey: hreflang tags on every page. Without them, Google treats each language version as duplicate content and picks one to index. The others never rank.\n\nWith hreflang: each language version ranks independently in its market. Spanish post ranks in Google.es, Arabic in Google.com.sa, etc.\n\nTraffic from non-English markets is now 40% of total. Would have been 0% without the hreflang setup.\n\n## Step 6: Measure, but do not obsess\n\nI check Search Console once a week. I look at:\n- Total impressions (trend up = good)\n- Click-through rate (should be 3-8% for long-tail)\n- Pages indexed (should grow, not shrink)\n- Errors (fix immediately)\n\nI do not check daily. Daily numbers are noise. Weekly numbers are signal.\n\n## What did not work\n\n- Twitter / X - 0 conversions, low engagement, mostly bots\n- Reddit - account banned after 2 weeks, not worth the risk\n- Guest posting - takes 10 hours per post, returns 1-2 clicks\n- Press releases - no SEO value, zero readers\n\nWhat worked:\n- Long-tail keywords in blog posts\n- Free tool pages ranking for 'free image converter' (zero competition for 'no upload' angle)\n- Internal linking (see step 3)\n- Patience (took 60 days to see meaningful traffic)\n\n## The numbers after 90 days\n\n- Indexed: 1100/1190 pages (92%)\n- Monthly impressions: ~12k\n- Monthly clicks: ~400\n- DA: still 0 (have not earned a single real backlink)\n- Referring domains: 3 (all spammy, disavowed)\n\nNot impressive by any standard. But the curve is exponential, not linear. Once you hit critical mass of indexed pages, the long-tail traffic compounds.\n\n## The 3 things I would do differently\n\n1. Skip Medium / Substack - they all canonicalize to themselves. Your content ranks on their domain, not yours. Build on your own site.\n2. Write less, write better - I killed 30 posts that were under 800 words. The remaining 135 outperform them 5:1.\n3. Ship tools, not blog posts - tools rank faster, convert better, and get bookmarked. Blog posts are the supporting cast.\n\n## If I had to start over with $0\n\n1. Pick a niche. Mine is 'free browser-based tools.'\n2. Build 10 tools that solve 10 real problems. Skip the ones that already exist with strong competitors.\n3. Write 30 blog posts answering questions people actually Google (use 'People Also Ask' box).\n4. Add hreflang for 2 languages max to start (English + your native language).\n5. Wait 90 days. Most people quit at day 30.\n\nThat is it. No tools, no budget, no shortcuts. Just good content + good structure + patience.\n\n---\n\nIf you want to see the site: &lt;a href="https://korelyy.com/en/" rel="noopener noreferrer"&gt;korelyy.com&lt;/a&gt; - 109 free tools, 165 blog posts, 6 languages, zero ads, zero tracking.&lt;/p&gt;

</description>
      <category>seowebdevbeginners</category>
    </item>
    <item>
      <title>MAI-Code-1-Flash Highlights Microsoft’s Lower-Cost Approach to Coding AI</title>
      <dc:creator>Ali Farhat</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:15:31 +0000</pubDate>
      <link>https://dev.to/alifar/mai-code-1-flash-highlights-microsofts-lower-cost-approach-to-coding-ai-oh</link>
      <guid>https://dev.to/alifar/mai-code-1-flash-highlights-microsofts-lower-cost-approach-to-coding-ai-oh</guid>
      <description>&lt;p&gt;Microsoft’s &lt;strong&gt;MAI-Code-1-Flash&lt;/strong&gt; is the more concrete story behind recent claims of &lt;a href="https://scalevise.com/resources/assess-20x-ai-performance-claim/" rel="noopener noreferrer"&gt;a cheaper, more efficient coding model&lt;/a&gt;. Announced at Build 2026, the model is a 5B active-parameter coding model designed for real developer workflows and integrated with GitHub Copilot and Visual Studio Code. Microsoft’s published materials emphasize lower latency, lower token use, and stronger code quality, although they do not document a separately released update with the exact 25% efficiency or cost figures circulating in recent discussion.&lt;/p&gt;

&lt;p&gt;The key point for developers and engineering leaders is that Microsoft is treating coding-model efficiency as a product capability, not simply a benchmark exercise. MAI-Code-1-Flash uses an adaptive thinking approach and is deployed through the GitHub Copilot harness and VS Code integration. That makes its performance in everyday coding tasks, including its token consumption, directly relevant to teams using AI assistance at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Microsoft has documented about MAI-Code-1-Flash
&lt;/h2&gt;

&lt;p&gt;Microsoft introduced MAI-Code-1-Flash on June 2, 2026 as part of a family of seven MAI models. According to &lt;a href="https://microsoft.ai/news/introducingmai-code-1-flash/" rel="noopener noreferrer"&gt;Microsoft’s MAI-Code-1-Flash launch announcement&lt;/a&gt;, the model was trained from scratch on clean enterprise data and without third-party distillation. Microsoft positions it as an inference-efficient coding model intended to deliver strong software-engineering performance at a lower cost profile than larger alternatives.&lt;/p&gt;

&lt;p&gt;The company has specifically compared its intended economics with Haiku, saying MAI-Code-1-Flash is designed to be cheaper while maintaining strong SWE-Bench results. That is a positioning claim rather than a published price card, and the supplied materials do not provide standalone API pricing, a public API rollout, or a detailed per-token rate for the model.&lt;/p&gt;

&lt;p&gt;Microsoft’s July 29 VS Code production-results post adds evidence from deployment. It says MAI-Code-1-Flash achieved &lt;strong&gt;higher code quality and lower token usage&lt;/strong&gt; in real developer workflows than other lightweight coding models. The official material also reports up to &lt;strong&gt;60% fewer tokens on SWE-Bench Verified&lt;/strong&gt;, with the associated cost benefit coming from token efficiency. On SWE-Bench Pro, Microsoft cites a 16-point lead in the referenced comparison.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Area&lt;/th&gt;
      &lt;th&gt;What official materials document&lt;/th&gt;
      &lt;th&gt;What remains unspecified&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Model design&lt;/td&gt;
      &lt;td&gt;5B active parameters, adaptive thinking, trained from scratch on clean enterprise data&lt;/td&gt;
      &lt;td&gt;Detailed architecture beyond the published description&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Developer delivery&lt;/td&gt;
      &lt;td&gt;Rollout to Copilot users in VS Code through the GitHub Copilot harness&lt;/td&gt;
      &lt;td&gt;A standalone public API availability announcement&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Efficiency evidence&lt;/td&gt;
      &lt;td&gt;Up to 60% fewer tokens on SWE-Bench Verified and lower token usage in production workflows&lt;/td&gt;
      &lt;td&gt;A documented 25% revision in efficiency or cost versus the June launch&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Cost positioning&lt;/td&gt;
      &lt;td&gt;Microsoft intends the model to be cheaper than Haiku through inference efficiency&lt;/td&gt;
      &lt;td&gt;Published model-specific pricing or a fixed fourfold reduction&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why token efficiency matters in developer tools
&lt;/h3&gt;

&lt;p&gt;Token use affects both response time and the operating cost of &lt;a href="https://scalevise.com/resources/ai-tools/" rel="noopener noreferrer"&gt;AI coding assistance&lt;/a&gt;. A model that reaches a useful answer with fewer tokens can reduce the computational work required for a task. In a Copilot-style environment, that can matter across code generation, debugging, refactoring, and multi-step engineering requests, where many individual interactions accumulate across a development organization.&lt;/p&gt;

&lt;p&gt;The model’s stated focus on real-world workflows is also significant. Coding benchmarks are useful indicators, but developer tools must work inside editors, repositories, and iterative human review cycles. Microsoft’s emphasis on production outcomes suggests that it is evaluating MAI-Code-1-Flash in the context where a lightweight model must be useful enough to earn its place alongside larger, potentially more expensive models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Availability and the broader MAI direction
&lt;/h3&gt;

&lt;p&gt;The documented rollout centers on &lt;a href="https://scalevise.com/resources/microsoft/" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub Copilot and Visual Studio Code&lt;/strong&gt;&lt;/a&gt;. That gives Microsoft a controlled distribution path: the company can deploy and assess the model within a widely used developer-assistance experience rather than relying only on a separate developer endpoint.&lt;/p&gt;

&lt;p&gt;MAI-Code-1-Flash also fits Microsoft’s broader MAI strategy, which includes Frontier Tuning and work with Mayo Clinic. Microsoft has described that strategy as building a system that can be adapted to users’ workflows across surfaces such as Excel and Copilot Chat. For the coding model, the practical implication is a focus on tailoring AI behavior to workflow context while managing inference efficiency.&lt;/p&gt;

&lt;p&gt;For now, teams should separate the official record from more precise performance claims that may emerge around subsequent iterations. The model launch and its VS Code production results are documented. A newly announced model revision with exact 25% improvement and cost-reduction figures is not established in the supplied official material.&lt;/p&gt;

&lt;p&gt;For businesses standardizing AI-assisted development, model efficiency affects both engineering experience and long-term spend. Scalevise can help assess where coding assistants fit into your delivery process, &lt;a href="https://scalevise.com/resources/ai-workflow-automation/" rel="noopener noreferrer"&gt;identify high-value automation opportunities&lt;/a&gt;, and design governance around real usage rather than benchmark headlines. A focused &lt;a href="https://scalevise.com/contact" rel="noopener noreferrer"&gt;AI workflow automation consultation&lt;/a&gt; can turn tool experimentation into a measurable implementation plan. &lt;strong&gt;Discuss an AI automation project with Scalevise.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is MAI-Code-1-Flash?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MAI-Code-1-Flash is Microsoft’s 5B active-parameter coding model. It is designed for inference-efficient developer workflows and is integrated with GitHub Copilot and Visual Studio Code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where is MAI-Code-1-Flash available?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Microsoft’s June 2026 materials state that the model is rolling out to Copilot users in Visual Studio Code. The supplied research does not document a standalone public API launch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What efficiency results has Microsoft published?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Microsoft reports up to 60% fewer tokens on SWE-Bench Verified. Its VS Code production-results material also describes higher code quality and lower token usage than other lightweight coding models in real developer workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Has Microsoft published a 25% cost reduction for a new MAI-Code-1-Flash version?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The supplied official materials do not document an update with that exact figure. They attribute lower cost potential to token efficiency and position the model as intended to be cheaper than Haiku.&lt;/p&gt;




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

&lt;p&gt;MAI-Code-1-Flash gives Microsoft a lightweight, workflow-oriented coding model inside GitHub Copilot and VS Code. Its documented results make token efficiency central to the model’s value proposition, while the exact claims attached to a possible newer iteration remain outside the supplied official record. The most meaningful development is Microsoft’s effort to pair coding quality with lower-cost inference in tools developers already use.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>tools</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>I Made an AI Project Manager PMI-Aligned—Without Turning It Into Bureaucracy</title>
      <dc:creator>yysun</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:12:41 +0000</pubDate>
      <link>https://dev.to/yysun/i-made-an-ai-project-manager-pmi-aligned-without-turning-it-into-bureaucracy-3c75</link>
      <guid>https://dev.to/yysun/i-made-an-ai-project-manager-pmi-aligned-without-turning-it-into-bureaucracy-3c75</guid>
      <description>&lt;p&gt;We’ve started working with AI like a colleague, not just using it as a tool.&lt;/p&gt;

&lt;p&gt;We ask it to plan work, coordinate people, surface risks, and tell us what should happen next. But an AI colleague with that much responsibility needs more than intelligence. It needs professional discipline.&lt;/p&gt;

&lt;p&gt;That is why I made &lt;a href="https://github.com/yysun/project-manager" rel="noopener noreferrer"&gt;Project Manager&lt;/a&gt; &lt;strong&gt;PMBOK 7 principles-aligned through documented tailoring&lt;/strong&gt;: so an AI PM can operate with autonomy without confusing confidence for evidence—or rigor for bureaucracy.&lt;/p&gt;

&lt;p&gt;This is not a claim of PMI certification. It is a concrete design decision about how the system decides which management disciplines apply, which do not, and how those decisions stay honest.&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%2F5uee9sqeto1cdgfjy9l1.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%2F5uee9sqeto1cdgfjy9l1.jpg" alt="An AI project manager that works like an employee" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The employee model changes the product boundary
&lt;/h2&gt;

&lt;p&gt;A chatbot attached to a task board still leaves the user operating the project-management software.&lt;/p&gt;

&lt;p&gt;You move cards. You update fields. You repair dependencies. You rebuild the report. The chat box may make each operation easier, but you still translate project reality into tool operations.&lt;/p&gt;

&lt;p&gt;An AI PM employee should reverse that relationship.&lt;/p&gt;

&lt;p&gt;You should be able to say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The vendor API is delayed until September 15. The launch date cannot move. Show me the credible options.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That statement may affect dependencies, schedules, risks, scope, ownership, evidence, and the next decision. The AI PM should trace those consequences together and maintain the working plan.&lt;/p&gt;

&lt;p&gt;That requires autonomy.&lt;/p&gt;

&lt;p&gt;It also creates risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Autonomy without discipline creates confident chaos
&lt;/h2&gt;

&lt;p&gt;A language model can produce a plausible plan even when ownership is missing, dates are unsupported, and completion has no evidence.&lt;/p&gt;

&lt;p&gt;The output may look polished while the project underneath it is incoherent.&lt;/p&gt;

&lt;p&gt;The answer is not to bury every project under a heavyweight process. That would destroy the reason to use an AI PM in the first place.&lt;/p&gt;

&lt;p&gt;The answer is to make process a &lt;strong&gt;deliberate, inspectable decision&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is where PMI tailoring becomes useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  “PMI aligned” needs a precise meaning
&lt;/h2&gt;

&lt;p&gt;Project Manager is aligned with PMBOK 7 principles through documented tailoring.&lt;/p&gt;

&lt;p&gt;PMBOK 7 makes tailoring central: the management approach should fit the project rather than forcing every project through the same machinery.&lt;/p&gt;

&lt;p&gt;For a concrete declaration, Project Manager uses the ten familiar PMBOK 6 knowledge areas. Each area must be recorded as one of two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Applied&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailored out&lt;/strong&gt;, with a rationale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A discipline cannot disappear silently.&lt;/p&gt;

&lt;p&gt;That distinction matters. A small internal project may genuinely have no procurement activity or dedicated cost budget. Omitting those areas can be correct. Omitting them because nobody considered them is not the same thing.&lt;/p&gt;

&lt;p&gt;A tailoring declaration might look conceptually like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;Rationale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Integration&lt;/td&gt;
&lt;td&gt;Applied&lt;/td&gt;
&lt;td&gt;Changes must propagate across the plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;Applied&lt;/td&gt;
&lt;td&gt;Success criteria and boundaries must remain explicit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Risk&lt;/td&gt;
&lt;td&gt;Applied&lt;/td&gt;
&lt;td&gt;Launch depends on an external vendor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Tailored out&lt;/td&gt;
&lt;td&gt;Work is absorbed by the standing team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Procurement&lt;/td&gt;
&lt;td&gt;Tailored out&lt;/td&gt;
&lt;td&gt;No external purchasing is involved&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The useful part is not the table. It is the fact that every omission becomes a recorded decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tailoring is not checkbox theatre
&lt;/h2&gt;

&lt;p&gt;A declaration is worthless if the system lets the project contradict it.&lt;/p&gt;

&lt;p&gt;Project Manager therefore fails validation when the project says an area is tailored out but configures the corresponding management structure.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tailor out risk management, then add a risk register: validation fails.&lt;/li&gt;
&lt;li&gt;Tailor out stakeholder management, then add a stakeholder register: validation fails.&lt;/li&gt;
&lt;li&gt;Claim an area is applied, then report missing information as zero or “on track”: not allowed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The declaration cannot become fiction.&lt;/p&gt;

&lt;p&gt;This also changes reporting. A tailored-out area is reported as tailored out with its rationale. It is never silently omitted, represented as zero, or painted green.&lt;/p&gt;

&lt;p&gt;“Cost: not tracked—no project budget; work is absorbed by the standing team” is honest.&lt;/p&gt;

&lt;p&gt;“Cost: $0” may be completely false.&lt;/p&gt;

&lt;h2&gt;
  
  
  The system expands only when the project needs it
&lt;/h2&gt;

&lt;p&gt;The smallest Project Manager project still has three files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROJECT.md
TASKS.md
STATUS.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is enough to define the outcome, organize the work, and derive current status.&lt;/p&gt;

&lt;p&gt;Additional management modules appear only when they answer a real operating need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;assumptions&lt;/li&gt;
&lt;li&gt;issues&lt;/li&gt;
&lt;li&gt;risks&lt;/li&gt;
&lt;li&gt;stakeholders&lt;/li&gt;
&lt;li&gt;decisions&lt;/li&gt;
&lt;li&gt;milestones&lt;/li&gt;
&lt;li&gt;lessons learned&lt;/li&gt;
&lt;li&gt;closure records&lt;/li&gt;
&lt;li&gt;traceability&lt;/li&gt;
&lt;li&gt;source and change history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the practical value of tailoring: rigor without mandatory ceremony.&lt;/p&gt;

&lt;p&gt;A project can stay small without looking negligent. A controlled project can add the structures it needs without changing the underlying operating model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence is the counterweight to agent confidence
&lt;/h2&gt;

&lt;p&gt;PMI alignment addresses management discipline. Evidence addresses execution truth.&lt;/p&gt;

&lt;p&gt;An AI agent saying “done” is not proof that a task met its acceptance criteria.&lt;/p&gt;

&lt;p&gt;Project Manager separates ordinary presentation from a stricter internal lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;planned  ready  in_progress  implemented  verification  verified  done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Completion must be supported by evidence mapped to the task’s acceptance criteria.&lt;/p&gt;

&lt;p&gt;A closed issue is not automatically proof.&lt;/p&gt;

&lt;p&gt;A commit is not automatically proof.&lt;/p&gt;

&lt;p&gt;A file existing is not automatically proof.&lt;/p&gt;

&lt;p&gt;Confident prose is definitely not proof.&lt;/p&gt;

&lt;p&gt;The AI PM can coordinate people, agents, external systems, and software workflows, but they meet at the same boundary: a task contract defines the expected result, and an evidence manifest proves what happened.&lt;/p&gt;

&lt;p&gt;That is how autonomy stays accountable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unknown must remain unknown
&lt;/h2&gt;

&lt;p&gt;Project-management software is good at displaying false precision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;confident dates without a real schedule&lt;/li&gt;
&lt;li&gt;green status without acceptance evidence&lt;/li&gt;
&lt;li&gt;percentage complete without a defensible calculation&lt;/li&gt;
&lt;li&gt;zero cost when cost was never tracked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An AI PM makes this problem worse if it treats every missing fact as an invitation to generate one.&lt;/p&gt;

&lt;p&gt;Project Manager instead keeps unknown information explicit. It should challenge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a target date with no schedule evidence&lt;/li&gt;
&lt;li&gt;completion with no acceptance evidence&lt;/li&gt;
&lt;li&gt;work marked ready while a dependency is unfinished&lt;/li&gt;
&lt;li&gt;a plan that does not cover its success criteria&lt;/li&gt;
&lt;li&gt;a combination of scope, capacity, and date that cannot all hold&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful project manager exposes the conflict. It does not decorate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is deliberately not implemented
&lt;/h2&gt;

&lt;p&gt;“PMI aligned” should not become a license to imply capabilities that do not exist.&lt;/p&gt;

&lt;p&gt;Project Manager does not currently implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cost accounting&lt;/li&gt;
&lt;li&gt;Earned Value Management&lt;/li&gt;
&lt;li&gt;critical-path scheduling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a project needs those disciplines, they must be tailored out with a rationale or managed in another system with that boundary recorded.&lt;/p&gt;

&lt;p&gt;That limitation is part of the design, not an embarrassing footnote.&lt;/p&gt;

&lt;p&gt;A trustworthy AI PM should state where its authority ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  The larger idea
&lt;/h2&gt;

&lt;p&gt;The interesting shift is not from manual task management to AI-assisted task management.&lt;/p&gt;

&lt;p&gt;It is from software you operate to an AI employee that maintains a coherent project with you.&lt;/p&gt;

&lt;p&gt;You provide the outcome, constraints, evidence, and decisions that require human authority.&lt;/p&gt;

&lt;p&gt;The AI PM keeps the plan aligned with reality.&lt;/p&gt;

&lt;p&gt;PMI-aligned tailoring gives that employee discipline without forcing every project into the same process. Evidence-backed completion keeps its confidence from becoming project truth.&lt;/p&gt;

&lt;p&gt;That combination—&lt;strong&gt;autonomy, tailoring, and evidence&lt;/strong&gt;—is what makes an AI project manager credible.&lt;/p&gt;

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

&lt;p&gt;Project Manager is open source:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yysun/project-manager" rel="noopener noreferrer"&gt;github.com/yysun/project-manager&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In your AI agent app, ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install the project-manager skill from yysun/project-manager.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then brief it like a colleague:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We need to launch the new website by October 15 without interrupting the current site. Rollback must be proven before launch. Establish the project, challenge anything vague, and surface the decisions I need to make.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Do not start with the board configuration.&lt;/p&gt;

&lt;p&gt;Start with reality.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>agents</category>
    </item>
    <item>
      <title>47 free AI tools that live in your browser (2026)</title>
      <dc:creator>CarsonJ</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:10:12 +0000</pubDate>
      <link>https://dev.to/korelyy/47-free-ai-tools-that-live-in-your-browser-2026-3h25</link>
      <guid>https://dev.to/korelyy/47-free-ai-tools-that-live-in-your-browser-2026-3h25</guid>
      <description>&lt;p&gt;I built 47 small AI helpers that run entirely in your browser. No signup. No upload. No tracking. They live at &lt;a href="https://korelyy.com/en/" rel="noopener noreferrer"&gt;Korelyy&lt;/a&gt; ? a tool garden I've been curating for the past few months.&lt;/p&gt;

&lt;p&gt;Here's why I went this route, and what's in there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why browser-only?
&lt;/h2&gt;

&lt;p&gt;Every AI tool I tried wanted the same three things: my email, my files, and my patience. So I started porting the ones I actually use to plain HTML + vanilla JS. They run offline, they never phone home, and they cost me nothing to host.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the box
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Writing &amp;amp; text&lt;/strong&gt; (8 tools)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Character counter that shows platform limits (X / LinkedIn / Meta)&lt;/li&gt;
&lt;li&gt;Case converter (Title / sentence / camelCase)&lt;/li&gt;
&lt;li&gt;Whitespace cleaner&lt;/li&gt;
&lt;li&gt;Markdown previewer&lt;/li&gt;
&lt;li&gt;Lorem generator&lt;/li&gt;
&lt;li&gt;Typographic quotes fixer&lt;/li&gt;
&lt;li&gt;Diff checker (side-by-side)&lt;/li&gt;
&lt;li&gt;Text statistics (readability + grade level)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Image &amp;amp; files&lt;/strong&gt; (11 tools)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Image converter (PNG / JPG / WebP)&lt;/li&gt;
&lt;li&gt;Image to PDF&lt;/li&gt;
&lt;li&gt;PDF to images&lt;/li&gt;
&lt;li&gt;Image resizer (aspect-ratio aware)&lt;/li&gt;
&lt;li&gt;Image cropper&lt;/li&gt;
&lt;li&gt;EXIF stripper&lt;/li&gt;
&lt;li&gt;SVG optimizer&lt;/li&gt;
&lt;li&gt;Favicon generator (16/32/180/192/512)&lt;/li&gt;
&lt;li&gt;QR code generator&lt;/li&gt;
&lt;li&gt;Barcode generator&lt;/li&gt;
&lt;li&gt;HEIC to JPG&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Calculators &amp;amp; life&lt;/strong&gt; (12 tools)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Life weeks (how many Saturdays left)&lt;/li&gt;
&lt;li&gt;Life progress bar&lt;/li&gt;
&lt;li&gt;Sleep cycle calculator&lt;/li&gt;
&lt;li&gt;Tip calculator&lt;/li&gt;
&lt;li&gt;Loan calculator&lt;/li&gt;
&lt;li&gt;Compound interest&lt;/li&gt;
&lt;li&gt;Age in days&lt;/li&gt;
&lt;li&gt;Date diff&lt;/li&gt;
&lt;li&gt;Working days calculator&lt;/li&gt;
&lt;li&gt;BMI&lt;/li&gt;
&lt;li&gt;Calorie estimator&lt;/li&gt;
&lt;li&gt;Random picker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dev helpers&lt;/strong&gt; (9 tools)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON formatter / validator&lt;/li&gt;
&lt;li&gt;JWT decoder&lt;/li&gt;
&lt;li&gt;UUID generator&lt;/li&gt;
&lt;li&gt;Hash (MD5/SHA-1/SHA-256)&lt;/li&gt;
&lt;li&gt;Base64 encoder/decoder&lt;/li&gt;
&lt;li&gt;URL encoder&lt;/li&gt;
&lt;li&gt;Color picker (with contrast checker)&lt;/li&gt;
&lt;li&gt;CSS unit converter&lt;/li&gt;
&lt;li&gt;HTML entity encoder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AI / productivity&lt;/strong&gt; (7 tools)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Emoji mixer (combine any two)&lt;/li&gt;
&lt;li&gt;Tone rewriter (formal / casual / friendly)&lt;/li&gt;
&lt;li&gt;Meeting agenda template&lt;/li&gt;
&lt;li&gt;Standup notes generator&lt;/li&gt;
&lt;li&gt;Daily planner&lt;/li&gt;
&lt;li&gt;Pomodoro timer&lt;/li&gt;
&lt;li&gt;Habit tracker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total: 47.&lt;/p&gt;

&lt;h2&gt;
  
  
  How they're built
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;100% client-side. The server only ships static files.&lt;/li&gt;
&lt;li&gt;No analytics, no cookies, no third-party calls.&lt;/li&gt;
&lt;li&gt;Most are under 300 lines of vanilla JS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A note on "free"
&lt;/h2&gt;

&lt;p&gt;There's no catch, no upsell, no "premium" tier hidden behind a paywall. The whole point was building things I would use myself. If a tool saves you five minutes a week, that's the deal.&lt;/p&gt;

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

&lt;p&gt;Browse the full collection at &lt;a href="https://korelyy.com/en/" rel="noopener noreferrer"&gt;korelyy.com/en&lt;/a&gt;. Available in 6 languages: English, ??, Espa?ol, Fran?ais, ??????, and ???????.&lt;/p&gt;

&lt;p&gt;If a tool is missing or broken, drop me a line. I'm shipping new ones every week.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>productivitytopensourceools</category>
    </item>
    <item>
      <title>Building AI-Powered Flutter Apps with Gemini</title>
      <dc:creator>vmodal_ai</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:02:59 +0000</pubDate>
      <link>https://dev.to/vmodal_ai/building-ai-powered-flutter-apps-with-gemini-10gj</link>
      <guid>https://dev.to/vmodal_ai/building-ai-powered-flutter-apps-with-gemini-10gj</guid>
      <description>&lt;h1&gt;
  
  
  Building AI-Powered Flutter Apps with Gemini
&lt;/h1&gt;

&lt;p&gt;Generative AI can be integrated into Flutter applications for chat, summarization, classification, content generation, document processing, and intelligent assistants.&lt;/p&gt;

&lt;p&gt;A production application should avoid putting sensitive API credentials directly in the mobile application.&lt;/p&gt;

&lt;p&gt;A safer architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Flutter App
    |
    | HTTPS
    v
Backend API
    |
    v
Gemini API
    |
    v
AI Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why use a backend?
&lt;/h2&gt;

&lt;p&gt;Embedding a production AI API key directly in an APK makes it possible for attackers to extract the key.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Flutter -&amp;gt; FastAPI/Node/Laravel -&amp;gt; Gemini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend can implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;rate limiting&lt;/li&gt;
&lt;li&gt;prompt templates&lt;/li&gt;
&lt;li&gt;logging&lt;/li&gt;
&lt;li&gt;usage limits&lt;/li&gt;
&lt;li&gt;response validation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Create the Flutter UI
&lt;/h2&gt;

&lt;p&gt;A simple chat screen can contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextEditingController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nl"&gt;controller:&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;decoration:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;InputDecoration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;hintText:&lt;/span&gt; &lt;span class="s"&gt;'Ask something...'&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;Send the message through a repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Call your backend here.&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;UnimplementedError&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;h2&gt;
  
  
  Step 2: Define a clean architecture
&lt;/h2&gt;

&lt;p&gt;A scalable Flutter AI application can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Presentation
    |
BLoC / Cubit
    |
AI Repository
    |
API Client
    |
Backend
    |
Gemini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it possible to replace Gemini later without rewriting the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Backend example with FastAPI
&lt;/h2&gt;

&lt;p&gt;Install dependencies:&lt;br&gt;
&lt;/p&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;fastapi uvicorn google-genai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GEMINI_API_KEY&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the API key in an environment variable:&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;GEMINI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never commit it to Git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Call the backend from Flutter
&lt;/h2&gt;

&lt;p&gt;Using the &lt;code&gt;http&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'dart:convert'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:http/http.dart'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiApi&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;AiApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$baseUrl&lt;/span&gt;&lt;span class="s"&gt;/chat'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nl"&gt;headers:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;'Content-Type'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;jsonEncode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="s"&gt;'message'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'AI request failed'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'response'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;String&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;h2&gt;
  
  
  Step 5: Add loading and error states
&lt;/h2&gt;

&lt;p&gt;AI requests are asynchronous, so explicitly represent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Idle
  ↓
Loading
  ↓
Success

or

Loading
  ↓
Failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With BLoC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiState&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiInitial&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;AiState&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiLoading&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;AiState&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AiSuccess&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;AiState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;AiSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&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;class&lt;/span&gt; &lt;span class="nc"&gt;AiFailure&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;AiState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;AiFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;message&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;h2&gt;
  
  
  Prompt engineering
&lt;/h2&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Use structured instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an assistant for a Flutter developer.

Task:
Explain the following Dart error.

Requirements:
1. Identify the root cause.
2. Provide corrected code.
3. Keep the explanation concise.

Error:
{{error}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Structured prompts make application behavior more predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming responses
&lt;/h2&gt;

&lt;p&gt;For chat applications, streaming can improve perceived responsiveness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User message
     |
     v
Backend
     |
     +---- token
     +---- token
     +---- token
     +---- token
     |
Flutter renders progressively
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consider Server-Sent Events or WebSockets depending on your backend architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security checklist
&lt;/h2&gt;

&lt;p&gt;Never expose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gemini API keys&lt;/li&gt;
&lt;li&gt;database credentials&lt;/li&gt;
&lt;li&gt;private backend secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;request validation&lt;/li&gt;
&lt;li&gt;rate limiting&lt;/li&gt;
&lt;li&gt;logging&lt;/li&gt;
&lt;li&gt;abuse protection&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Flutter is an excellent client platform for AI applications, but a maintainable architecture separates the UI from the AI provider. A backend gives you control over security, prompts, model selection, cost, and business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful Links
&lt;/h2&gt;

&lt;p&gt;SDK Flutter: &lt;a href="https://github.com/v-modal/vmodal_sdk_flutter" rel="noopener noreferrer"&gt;https://github.com/v-modal/vmodal_sdk_flutter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SDK Android: &lt;a href="https://github.com/v-modal/vmodal_sdk_android" rel="noopener noreferrer"&gt;https://github.com/v-modal/vmodal_sdk_android&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Discord: &lt;a href="https://discord.gg/K72z28KUx" rel="noopener noreferrer"&gt;https://discord.gg/K72z28KUx&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>flutter</category>
      <category>mobile</category>
    </item>
    <item>
      <title>My Pest test passed because I added a helpful failure message</title>
      <dc:creator>Michael Yousrie</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:02:39 +0000</pubDate>
      <link>https://dev.to/michael-yousrie/my-pest-test-passed-because-i-added-a-helpful-failure-message-5hj2</link>
      <guid>https://dev.to/michael-yousrie/my-pest-test-passed-because-i-added-a-helpful-failure-message-5hj2</guid>
      <description>&lt;p&gt;I write marketing copy about my own products and I have a bad habit of leaving claims in it that stop being true. Prices change, a feature moves tier, and the blog post from March keeps happily saying the old thing. So I wrote a test that reads the published posts and asserts the dead claims are gone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;not&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'$29/mo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'pricing changed in June, this post is stale'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Green. Every one of them green. The string &lt;code&gt;$29/mo&lt;/code&gt; was still sitting in the post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What that second argument actually does
&lt;/h2&gt;

&lt;p&gt;Here is the signature, straight out of &lt;code&gt;vendor/pestphp/pest/src/Mixins/Expectation.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$needles&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$needles&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$needle&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="nb"&gt;is_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Assert&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;assertStringContainsString&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$needle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&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="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="nb"&gt;is_iterable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;InvalidExpectationValue&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'iterable'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nc"&gt;Assert&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;assertContains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$needle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&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="nv"&gt;$this&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;It is variadic. My careful explanatory message was never a message. It was needle number two.&lt;/p&gt;

&lt;p&gt;The positive expectation is therefore a conjunction: contains A &lt;strong&gt;and&lt;/strong&gt; contains B. Which means the negation is "missing at least one of them", and my sentence about June pricing was guaranteed to be missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How &lt;code&gt;not&lt;/code&gt; gets there
&lt;/h2&gt;

&lt;p&gt;Pest does not implement a separate negative assertion. &lt;code&gt;OppositeExpectation::__call&lt;/code&gt; runs the positive one and catches the failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$arguments&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Expectation&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ExpectationFailedException&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nc"&gt;AssertionFailedError&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="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// positive failed, so `not` succeeds&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;throwExpectationFailedException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$arguments&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;So follow my call through. The loop checks &lt;code&gt;$29/mo&lt;/code&gt;, finds it, passes. Then it checks &lt;code&gt;pricing changed in June, this post is stale&lt;/code&gt;, does not find it, throws. That throw is caught, and &lt;code&gt;not&lt;/code&gt; reports success.&lt;/p&gt;

&lt;p&gt;The first needle was checked. The result was thrown away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this one is so easy to walk into
&lt;/h2&gt;

&lt;p&gt;I counted the public methods on that mixin. There are 68 expectations. &lt;strong&gt;66 of them take a real message parameter.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="nv"&gt;$expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toBeFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;toStartWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;self&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two do not: &lt;code&gt;toContain&lt;/code&gt; and &lt;code&gt;toContainEqual&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;toStartWith&lt;/code&gt; is defined forty lines below &lt;code&gt;toContain&lt;/code&gt; in the same file, and it takes a message. So the habit is correct 66 times out of 68 and you are not misremembering the library, you are remembering the other 97% of it. At the call site a variadic parameter and an optional trailing string are the same keystrokes. Nothing about &lt;code&gt;('needle', 'my message')&lt;/code&gt; looks different from &lt;code&gt;('needle', 'other needle')&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four cases, run
&lt;/h2&gt;

&lt;p&gt;Pest 4.4.1 on PHP 8.4.13, value is &lt;code&gt;'hello world'&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;assertion&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;not-&amp;gt;toContain('zzz', 'qqq')&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;passes, correctly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;not-&amp;gt;toContain('hello', 'qqq')&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;passes&lt;/strong&gt;, and 'hello' is right there&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;not-&amp;gt;toContain('qqq', 'hello')&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;passes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;not-&amp;gt;toContain('hello')&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;fails, correctly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Only the single-needle form does what you read it as doing. Add anything after it and the assertion turns into a coin flip you always win.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I do now
&lt;/h2&gt;

&lt;p&gt;For the check I actually wanted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str_contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'$29/mo'&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;toBeFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pricing changed in June, this post is stale'&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;toBeFalse&lt;/code&gt; has a real &lt;code&gt;$message&lt;/code&gt;, so the sentence lands where I meant it to and the assertion is about one thing.&lt;/p&gt;

&lt;p&gt;The wider habit I picked up from this: if an assertion is load-bearing, make it fail once on purpose before you trust it. I had written a test specifically to catch a stale claim, the stale claim was present, and the test was green. Deleting the second argument turned it red immediately. That took ten seconds and I had not done it, because the test was passing and passing tests do not feel like they need investigating.&lt;/p&gt;

&lt;p&gt;That is the actual failure. Not the variadic signature, which is documented and reasonable. It is that a green test reads as evidence, and a test that cannot fail is green in exactly the same shade as a test that just did its job.&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Flutter Platform Channels: Calling Kotlin from Dart</title>
      <dc:creator>vmodal_ai</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:02:38 +0000</pubDate>
      <link>https://dev.to/vmodal_ai/flutter-platform-channels-calling-kotlin-from-dart-43p1</link>
      <guid>https://dev.to/vmodal_ai/flutter-platform-channels-calling-kotlin-from-dart-43p1</guid>
      <description>&lt;h1&gt;
  
  
  Flutter Platform Channels: Calling Kotlin from Dart
&lt;/h1&gt;

&lt;p&gt;Flutter gives you a single codebase for Android and iOS, but sometimes you need functionality that is specific to the native platform. This is where &lt;strong&gt;Platform Channels&lt;/strong&gt; become useful.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will build a simple Flutter-to-Kotlin bridge using &lt;code&gt;MethodChannel&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Platform Channels?
&lt;/h2&gt;

&lt;p&gt;A platform channel allows Dart code to communicate with native Android or iOS code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Flutter / Dart
      |
      | MethodChannel
      v
Android / Kotlin
      |
      v
Native API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flutter provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MethodChannel&lt;/code&gt; — request/response calls&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EventChannel&lt;/code&gt; — continuous streams of events&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BasicMessageChannel&lt;/code&gt; — asynchronous messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For calling a Kotlin function and receiving a result, &lt;code&gt;MethodChannel&lt;/code&gt; is usually the best choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create the Flutter project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter create platform_channel_demo
&lt;span class="nb"&gt;cd &lt;/span&gt;platform_channel_demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Create the Dart platform channel
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;lib/native_service.dart&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/services.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NativeService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MethodChannel&lt;/span&gt; &lt;span class="n"&gt;_channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;MethodChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'com.example.platform/native'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getPlatformMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_channel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeMethod&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
      &lt;span class="s"&gt;'getPlatformMessage'&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;'No response'&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;The channel name must match the channel name used by Kotlin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Call Kotlin from MainActivity
&lt;/h2&gt;

&lt;p&gt;Open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android/app/src/main/kotlin/.../MainActivity.kt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.platform_channel_demo&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.flutter.embedding.android.FlutterActivity&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.flutter.embedding.engine.FlutterEngine&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.flutter.plugin.common.MethodChannel&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FlutterActivity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;CHANNEL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.example.platform/native"&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;configureFlutterEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flutterEngine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;FlutterEngine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configureFlutterEngine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flutterEngine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nc"&gt;MethodChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;flutterEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dartExecutor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binaryMessenger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;CHANNEL&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setMethodCallHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;

            &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"getPlatformMessage"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello from Kotlin!"&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;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notImplemented&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;h2&gt;
  
  
  Step 4: Use it from Flutter
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/material.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:flutter/services.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'native_service.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomePage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_HomePageState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;_HomePageState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'Waiting...'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;loadMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;NativeService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPlatformMessage&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="n"&gt;mounted&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="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&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;on&lt;/span&gt; &lt;span class="n"&gt;PlatformException&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&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="n"&gt;mounted&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="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'Native error: &lt;/span&gt;&lt;span class="si"&gt;${e.message}&lt;/span&gt;&lt;span class="s"&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="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&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="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Platform Channel'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;mainAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;MainAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="n"&gt;loadMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Call Kotlin'&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Passing arguments
&lt;/h2&gt;

&lt;p&gt;Dart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_channel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;'greet'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'Nauman'&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;Kotlin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="s"&gt;"greet"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;"User"&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, $name!"&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;h2&gt;
  
  
  Returning structured data
&lt;/h2&gt;

&lt;p&gt;Kotlin can return maps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;mapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"platform"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="s"&gt;"Android"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"version"&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VERSION&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RELEASE&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;Dart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_channel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;invokeMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'deviceInfo'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'platform'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'version'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Error handling
&lt;/h2&gt;

&lt;p&gt;Native code should return meaningful errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;result&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="s"&gt;"DEVICE_ERROR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"Unable to access device information"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;NativeService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPlatformMessage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kd"&gt;on&lt;/span&gt; &lt;span class="n"&gt;PlatformException&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;message&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;h2&gt;
  
  
  Production recommendations
&lt;/h2&gt;

&lt;p&gt;Avoid scattering &lt;code&gt;MethodChannel&lt;/code&gt; calls throughout widgets. A cleaner architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UI
 |
BLoC / ViewModel
 |
Repository
 |
NativeService
 |
MethodChannel
 |
Kotlin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps platform-specific code isolated and testable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Channel names do not match
&lt;/h3&gt;

&lt;p&gt;These must be identical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;MethodChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'com.example.platform/native'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;CHANNEL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.example.platform/native"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Calling native code directly from many widgets
&lt;/h3&gt;

&lt;p&gt;Create a service or repository layer instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blocking the UI thread
&lt;/h3&gt;

&lt;p&gt;Native code should not perform long-running work on the Android main thread.&lt;/p&gt;

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

&lt;p&gt;Platform Channels are one of Flutter's most useful escape hatches when you need native Android functionality. A clean &lt;code&gt;MethodChannel&lt;/code&gt; abstraction lets you combine Flutter's UI productivity with Kotlin's native APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful Links
&lt;/h2&gt;

&lt;p&gt;SDK Flutter: &lt;a href="https://github.com/v-modal/vmodal_sdk_flutter" rel="noopener noreferrer"&gt;https://github.com/v-modal/vmodal_sdk_flutter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SDK Android: &lt;a href="https://github.com/v-modal/vmodal_sdk_android" rel="noopener noreferrer"&gt;https://github.com/v-modal/vmodal_sdk_android&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Discord: &lt;a href="https://discord.gg/K72z28KUx" rel="noopener noreferrer"&gt;https://discord.gg/K72z28KUx&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>flutter</category>
      <category>kotlin</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What Is Technical Documentation, and What Should It Include?</title>
      <dc:creator>Ninad Pathak</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:02:28 +0000</pubDate>
      <link>https://dev.to/ninadpathak/what-is-technical-documentation-and-what-should-it-include-29lc</link>
      <guid>https://dev.to/ninadpathak/what-is-technical-documentation-and-what-should-it-include-29lc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published at &lt;a href="https://ninadpathak.com/articles/what-is-technical-documentation-and-what-should-it-include/" rel="noopener noreferrer"&gt;https://ninadpathak.com/articles/what-is-technical-documentation-and-what-should-it-include/&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Technical documentation is the material that helps someone understand, use, integrate with, or safely operate a system. It works when a reader can take the next step without guessing.&lt;/p&gt;

&lt;p&gt;Think of it as a route through a product. A tutorial gets someone moving, reference answers an exact question, explanation gives a decision context, and troubleshooting helps when the route breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What technical documentation should include
&lt;/h2&gt;

&lt;p&gt;Start with the reader task, not a list of page types. A developer evaluating a product needs different information from someone sending a first API request or investigating an error.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reader task&lt;/th&gt;
&lt;th&gt;Document that owns it&lt;/th&gt;
&lt;th&gt;What must be visible&lt;/th&gt;
&lt;th&gt;What shows it is enough&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Decide whether the product fits&lt;/td&gt;
&lt;td&gt;Orientation or README&lt;/td&gt;
&lt;td&gt;Scope, non-scope, supported use cases, and a starting link&lt;/td&gt;
&lt;td&gt;The reader can name the first relevant path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complete a first integration&lt;/td&gt;
&lt;td&gt;Quickstart or tutorial&lt;/td&gt;
&lt;td&gt;Prerequisites, one supported path, expected output, and a failure link&lt;/td&gt;
&lt;td&gt;The reader reaches a recognizable result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Understand a system choice&lt;/td&gt;
&lt;td&gt;Explanation&lt;/td&gt;
&lt;td&gt;Terms, model, constraints, and links to task pages&lt;/td&gt;
&lt;td&gt;The reader can explain the choice before implementing it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change an existing behavior&lt;/td&gt;
&lt;td&gt;How-to guide&lt;/td&gt;
&lt;td&gt;Starting state, procedure, success state, and recovery boundary&lt;/td&gt;
&lt;td&gt;The reader can perform the change safely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Look up an exact question&lt;/td&gt;
&lt;td&gt;Reference&lt;/td&gt;
&lt;td&gt;Names, types, defaults, constraints, and exceptions&lt;/td&gt;
&lt;td&gt;The reader can find the answer without inference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diagnose a failure&lt;/td&gt;
&lt;td&gt;Troubleshooting&lt;/td&gt;
&lt;td&gt;Symptom, diagnostic check, cause, recovery, and escalation boundary&lt;/td&gt;
&lt;td&gt;The reader can choose the next safe action&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Respond to a product change&lt;/td&gt;
&lt;td&gt;Release note or migration guide&lt;/td&gt;
&lt;td&gt;Impact, required action, version or date, and a migration path&lt;/td&gt;
&lt;td&gt;The reader can decide whether action is required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The idea is, one page should not teach a beginner, document every field, explain the architecture, and debug production failures. Split those jobs so each page can answer its question completely.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://diataxis.fr/" rel="noopener noreferrer"&gt;Diátaxis framework&lt;/a&gt; separates tutorials, how-to guides, reference, and explanation for the same reason. The names help, but the reader task decides whether a page belongs in one category or another.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the smallest package that lets someone succeed
&lt;/h2&gt;

&lt;p&gt;A small team does not need seven finished document types before launch. It needs enough material for the first reader to understand the product, finish one supported task, and locate the stable details or recovery guidance that task depends on.&lt;/p&gt;

&lt;p&gt;For an API, that often means an orientation page, a quickstart, and reference for the endpoint or SDK used in that quickstart. Add troubleshooting when a failed setup has a meaningful diagnostic path, then add release material when product changes can affect an existing integration.&lt;/p&gt;

&lt;p&gt;For a CLI, orientation should explain the tool’s job and supported environments. The quickstart should produce one visible command result, while reference owns commands, flags, exit behavior, and configuration.&lt;/p&gt;

&lt;p&gt;An internal platform may need a different starting package. Access, a supported setup path, ownership, and the operating rules that change a team’s next action can matter before a public-style product portal.&lt;/p&gt;

&lt;p&gt;Use the &lt;a href="https://ninadpathak.com/articles/internal-vs-external-documentation/" rel="noopener noreferrer"&gt;internal vs. external documentation decision&lt;/a&gt; when the same subject needs both private operating context and a public task-focused treatment.&lt;/p&gt;

&lt;p&gt;Google’s &lt;a href="https://developers.google.com/tech-writing/one/documents" rel="noopener noreferrer"&gt;technical writing guidance&lt;/a&gt; recommends defining scope, non-scope, and audience before expanding a draft. That decision keeps a page from collecting unrelated jobs as it grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give each document a boundary
&lt;/h2&gt;

&lt;p&gt;An orientation page helps a reader decide where to begin. It should answer what the product does, who it is for, what it does not cover, and where the first supported task starts.&lt;/p&gt;

&lt;p&gt;A quickstart proves one supported path. It should link to reference when exact options matter and to troubleshooting when a failure would block the first result.&lt;/p&gt;

&lt;p&gt;An explanation page gives a model for a design choice. It should make the next task easier without forcing the reader to learn every concept before they can do useful work.&lt;/p&gt;

&lt;p&gt;A how-to guide changes one behavior from a known starting state. Keep details that remain stable across many tasks in reference, and keep recurring failures in troubleshooting.&lt;/p&gt;

&lt;p&gt;Reference owns the public API surface. Google’s &lt;a href="https://developers.google.com/style/api-reference-comments" rel="noopener noreferrer"&gt;API reference guidance&lt;/a&gt; calls out methods, parameters, returns, and exceptions because implementation depends on exact behavior rather than narrative approximation.&lt;/p&gt;

&lt;p&gt;Troubleshooting starts from a recognizable symptom and gives a diagnostic check, likely cause, safe recovery, and escalation boundary. A generic support link cannot tell someone whether a retry is safe.&lt;/p&gt;

&lt;p&gt;Release notes explain changed behavior and required action. They should link to the canonical guide or migration page instead of repeating every instruction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make coverage inspectable before writing prose
&lt;/h2&gt;

&lt;p&gt;A document list can hide missing ownership. I ran a small &lt;a href="https://ninadpathak.com/static/templates/documentation-deliverables-manifest.yaml" rel="noopener noreferrer"&gt;documentation deliverables manifest&lt;/a&gt; for a webhook API example so every planned page has one reader task, success signal, owner, review trigger, and deferral condition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;.venv/bin/python static/templates/validate_documentation_deliverables.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The validator passed with seven owned reader tasks. That result does not tell you whether every paragraph is good, but it does expose a missing task owner before a repository fills with overlapping pages.&lt;/p&gt;

&lt;p&gt;Documentation deliverables manifest validation&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;img src="/static/images/articles/what-is-technical-documentation-and-what-should-it-include/documentation-deliverables-manifest.png?v=0c2f9fe6" alt="Terminal output showing a documentation deliverables manifest with seven owned reader tasks passed validation"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The validator checks each planned deliverable for a reader task, completion signal, owner, update trigger, and deferral condition.&lt;/p&gt;

&lt;p&gt;Use this kind of check before writing a documentation site from scratch. It is planning evidence, not a replacement for task testing, source checks, or editorial review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assign an owner and an update trigger
&lt;/h2&gt;

&lt;p&gt;A review date alone does not keep documentation current. Give each deliverable an owner and a product change that requires review, such as a schema change, permission change, new release behavior, support issue, or incident.&lt;/p&gt;

&lt;p&gt;GitLab’s &lt;a href="https://docs.gitlab.com/development/documentation/" rel="noopener noreferrer"&gt;documentation guidance&lt;/a&gt; treats documentation as maintained product material for configuring, using, and troubleshooting a system. Update the page when product behavior makes its existing instructions unsafe or incomplete.&lt;/p&gt;

&lt;p&gt;A useful review trigger connects the document to the change that can invalidate it. A schema change should prompt reference review, a revised onboarding flow should prompt quickstart review, and a recurring failure should prompt troubleshooting work.&lt;/p&gt;

&lt;p&gt;Use a &lt;a href="https://ninadpathak.com/articles/technical-documentation-template/" rel="noopener noreferrer"&gt;technical documentation template&lt;/a&gt; when you are ready to turn these decisions into a repository. Then &lt;a href="https://ninadpathak.com/articles/technical-documentation-best-practices-tested-real-developer-docs/" rel="noopener noreferrer"&gt;test whether a page helps someone finish a task&lt;/a&gt; before calling it complete.&lt;/p&gt;

&lt;p&gt;Use the &lt;a href="https://ninadpathak.com/articles/documentation-review-checklist-before-you-publish/" rel="noopener noreferrer"&gt;documentation review checklist&lt;/a&gt; before publication. If duplicate or stale answers are the problem, start by &lt;a href="https://ninadpathak.com/articles/how-to-organize-a-documentation-site/" rel="noopener noreferrer"&gt;organizing documentation that has drifted&lt;/a&gt; instead of adding another page.&lt;/p&gt;

</description>
      <category>developers</category>
      <category>documentation</category>
      <category>software</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>DDD no seu CRUD é canhão pra matar mosquito</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:01:57 +0000</pubDate>
      <link>https://dev.to/denisgusto1/ddd-no-seu-crud-e-canhao-pra-matar-mosquito-2jb6</link>
      <guid>https://dev.to/denisgusto1/ddd-no-seu-crud-e-canhao-pra-matar-mosquito-2jb6</guid>
      <description>&lt;h2&gt;
  
  
  Sete arquivos pra salvar um cadastro
&lt;/h2&gt;

&lt;p&gt;Peguei um projeto uma vez pra fazer uma alteração pequena: adicionar o campo "telefone secundário" no cadastro de cliente. Coisa de dez minutos, né?&lt;/p&gt;

&lt;p&gt;Uma hora e meia depois eu tinha mexido em: a entidade &lt;code&gt;Cliente&lt;/code&gt;, o value object &lt;code&gt;Telefone&lt;/code&gt;, o DTO de entrada, o mapper que traduz DTO pra entidade, a interface do repositório, a implementação Eloquent do repositório, o use case &lt;code&gt;AtualizarClienteUseCase&lt;/code&gt;, e o request do controller.&lt;/p&gt;

&lt;p&gt;Oito arquivos. Pra um campo de texto que vai direto pra uma coluna &lt;code&gt;varchar&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;O sistema não fazia nada de mirabolante com esse cliente. Cadastrava, listava, editava, apagava. Um CRUD. Mas estava vestido com o terno de um sistema bancário.&lt;/p&gt;

&lt;p&gt;Já pegou um projeto assim? Ou — pergunta mais desconfortável — já &lt;strong&gt;escreveu&lt;/strong&gt; um?&lt;/p&gt;

&lt;h2&gt;
  
  
  O que parece sofisticação e é só cerimônia
&lt;/h2&gt;

&lt;p&gt;Olha o caminho que um &lt;code&gt;POST /clientes&lt;/code&gt; percorria naquele projeto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Controller recebe e converte pra DTO&lt;/span&gt;
&lt;span class="nv"&gt;$dto&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;CriarClienteDTO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;telefone&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;telefone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Use case recebe o DTO&lt;/span&gt;
&lt;span class="nv"&gt;$useCase&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;executar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Dentro do use case: DTO vira entidade de domínio&lt;/span&gt;
&lt;span class="nv"&gt;$cliente&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;ClienteEntity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;nome&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;Nome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;email&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;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;telefone&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;Telefone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dto&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;telefone&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Repositório recebe a entidade&lt;/span&gt;
&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;repositorio&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;salvar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cliente&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 5. Dentro do repositório: entidade vira Model&lt;/span&gt;
&lt;span class="nv"&gt;$model&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;ClienteModel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cliente&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cliente&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;telefone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cliente&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;telefone&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$model&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// 6. E aí volta tudo, agora no sentido contrário 🙃&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cinco traduções do mesmo dado. Nome vira &lt;code&gt;Nome&lt;/code&gt;, &lt;code&gt;Nome&lt;/code&gt; vira string de novo, string vira coluna.&lt;/p&gt;

&lt;p&gt;Agora a pergunta honesta: &lt;strong&gt;qual bug isso evitou?&lt;/strong&gt; Qual regra de negócio ficou protegida? Nesse projeto, nenhuma. O &lt;code&gt;ClienteEntity&lt;/code&gt; era um saco de propriedades sem nenhum comportamento — exatamente o que a gente acusa de "modelo anêmico", só que espalhado em quatro camadas.&lt;/p&gt;

&lt;h2&gt;
  
  
  DDD nunca foi sobre pastas
&lt;/h2&gt;

&lt;p&gt;Aqui mora a confusão. A maior parte do que a galera chama de DDD é a &lt;strong&gt;parte tática&lt;/strong&gt;: entidade, value object, agregado, repositório, use case. É a parte fácil de copiar, porque é estrutura de pasta.&lt;/p&gt;

&lt;p&gt;Só que o DDD é sobre a parte &lt;strong&gt;estratégica&lt;/strong&gt;: entender o negócio a fundo, conversar com quem trabalha nele, descobrir que "pedido" pro pessoal do estoque não é a mesma coisa que "pedido" pro financeiro, e desenhar fronteiras a partir disso. Linguagem ubíqua, contextos delimitados.&lt;/p&gt;

&lt;p&gt;A estrutura de pastas é consequência disso. Não substituto.&lt;/p&gt;

&lt;p&gt;Quando você copia só as pastas, o que sobra é o custo — mais arquivos, mais indireção, mais tempo pra qualquer mudança — sem o benefício, que seria um código que fala a língua do negócio.&lt;/p&gt;

&lt;h2&gt;
  
  
  A régua: complexidade de dado ou complexidade de regra?
&lt;/h2&gt;

&lt;p&gt;A pergunta que eu faço hoje antes de montar qualquer camada:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Esse dado tem regra ou só tem formulário?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Se o fluxo é "recebe do form, valida formato, salva no banco, mostra numa listagem", isso é &lt;strong&gt;complexidade de dado&lt;/strong&gt;. Laravel já resolve isso lindamente com Form Request, Eloquent e Resource. Adicionar camada aí só aumenta a distância entre você e o &lt;code&gt;varchar&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Agora, se o fluxo é "recebe a solicitação, verifica se o cliente tem limite, calcula juros conforme a modalidade, aplica desconto de fidelidade, agenda a cobrança e emite o título" — aí é &lt;strong&gt;complexidade de regra&lt;/strong&gt;. É aqui que separar as coisas te salva, porque a regra vive mais tempo que o framework e muda por motivos diferentes.&lt;/p&gt;

&lt;p&gt;E o ponto que quase ninguém fala: &lt;strong&gt;os dois convivem no mesmo projeto&lt;/strong&gt;. Não precisa escolher.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como fica na prática
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;O CRUD continua CRUD.&lt;/strong&gt; Sem culpa nenhuma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClienteController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;StoreClienteRequest&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$cliente&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cliente&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;validated&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ClienteResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cliente&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;Três linhas. Todo mundo entende, inclusive o dev que entrar no time semana que vem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O que tem regra ganha uma Action:&lt;/strong&gt; o caminho do meio que resolve 90% dos casos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AprovarEmprestimo&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Emprestimo&lt;/span&gt; &lt;span class="nv"&gt;$emprestimo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$analista&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// aqui mora a regra, isolada e testável, sem quatro camadas&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;strong&gt;E as peças táticas você usa avulso, onde valem a pena.&lt;/strong&gt; Value Object pra &lt;code&gt;CPF&lt;/code&gt; e &lt;code&gt;Dinheiro&lt;/code&gt;, Enum pra status, DTO na fronteira de integração. Não é pacote fechado — dá pra pegar só as ferramentas que resolvem o seu problema.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha: "mas e quando o projeto crescer?"
&lt;/h2&gt;

&lt;p&gt;Esse é o argumento que eu mais ouço. E ele tem um erro escondido: assume que refatorar depois é mais caro do que carregar a estrutura desde o começo.&lt;/p&gt;

&lt;p&gt;Quase nunca é. Transformar um controller gordo em Action é trabalho de uma tarde, e você faz isso &lt;strong&gt;sabendo&lt;/strong&gt; qual é a regra de verdade, porque o negócio já se revelou.&lt;/p&gt;

&lt;p&gt;O contrário é que dói: desmontar uma arquitetura de quatro camadas que ninguém entende e que já espalhou tradução de dado por trinta arquivos. Aí é semana, não tarde.&lt;/p&gt;

&lt;p&gt;E tem o custo que não aparece no gráfico: cada dev novo no time leva três semanas pra fazer o primeiro CRUD, porque precisa aprender o dialeto local antes de escrever a primeira linha.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: o teste do "conta pro estagiário"
&lt;/h2&gt;

&lt;p&gt;Antes de adicionar uma camada, tenta explicar em voz alta pra alguém que acabou de chegar &lt;strong&gt;qual problema aquilo resolve&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Se a resposta sair como "é pra desacoplar do framework" ou "é boa prática", segura a onda. Não é justificativa, é slogan.&lt;/p&gt;

&lt;p&gt;Se a resposta for "porque a regra de cálculo de comissão muda por estado e a gente precisa testar as 27 combinações sem tocar no banco" — aí sim. Isso é um problema concreto, e a camada tem endereço.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de você fechar a aba
&lt;/h2&gt;

&lt;p&gt;Não estou dizendo que DDD é ruim. Em domínio complexo de verdade, com regra de negócio que muda toda semana e gente do negócio discutindo termo por termo, ele é o que segura o sistema em pé.&lt;/p&gt;

&lt;p&gt;O que eu tô dizendo é que &lt;strong&gt;arquitetura é resposta a um problema&lt;/strong&gt;. Sem o problema, ela é só custo com aparência de sofisticação.&lt;/p&gt;

&lt;p&gt;Cadastro de cliente não precisa de camada anticorrupção. Precisa de &lt;code&gt;Cliente::create()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;E aí, você já pegou (ou construiu) um projeto vestido pra uma complexidade que ele não tinha? Ou é do time que acha que camada nunca é demais? Vem discutir nos comentários — esse tema divide a comunidade ao meio e eu quero ouvir os dois lados. 🍿&lt;/p&gt;




</description>
      <category>laravel</category>
      <category>php</category>
      <category>cleancode</category>
      <category>backend</category>
    </item>
    <item>
      <title>Code review: como apontar o problema sem parecer babaca</title>
      <dc:creator>Denis Augusto</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:01:56 +0000</pubDate>
      <link>https://dev.to/denisgusto1/code-review-como-apontar-o-problema-sem-parecer-babaca-19c5</link>
      <guid>https://dev.to/denisgusto1/code-review-como-apontar-o-problema-sem-parecer-babaca-19c5</guid>
      <description>&lt;h2&gt;
  
  
  O comentário tecnicamente correto que estraga o dia
&lt;/h2&gt;

&lt;p&gt;Você abre o pull request e lá está:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Isso tá errado."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Três palavras. Tecnicamente, pode até estar certíssimo. Mas o efeito na pessoa do outro lado é o mesmo de levar um empurrão no corredor.&lt;/p&gt;

&lt;p&gt;E aí o PR trava. A pessoa responde defendendo a escolha. Você responde explicando melhor. Ela responde meio seca. Dois dias depois o código sai igual ao que ia sair, só que agora tem um clima estranho na daily.&lt;/p&gt;

&lt;p&gt;Já viveu isso? De um lado ou do outro? Eu já vivi dos dois — e o lado de quem escreve o comentário ruim é mais fácil de estar do que a gente imagina.&lt;/p&gt;

&lt;h2&gt;
  
  
  O problema é o meio, não a maldade
&lt;/h2&gt;

&lt;p&gt;Quase ninguém escreve comentário de review querendo ferir. O problema é estrutural.&lt;/p&gt;

&lt;p&gt;Code review é &lt;strong&gt;texto&lt;/strong&gt;, &lt;strong&gt;assíncrono&lt;/strong&gt; e &lt;strong&gt;público&lt;/strong&gt;. Não tem tom de voz, não tem cara, não tem o "risinho" que na mesa amaciaria a frase. E o time inteiro lê.&lt;/p&gt;

&lt;p&gt;Quando falta tom, quem lê &lt;strong&gt;preenche sozinho&lt;/strong&gt;. E a pessoa que acabou de passar seis horas naquele código, insegura sobre o resultado, vai preencher pro lado ruim. Sempre.&lt;/p&gt;

&lt;p&gt;O mesmo comentário dito de pé, ao lado da mesa, com uma pausa e um "cara, olha só", passa liso. Escrito, vira julgamento.&lt;/p&gt;

&lt;p&gt;Então não é sobre ser bonzinho. É sobre compensar o que o meio tira.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sete coisas que mudaram meus reviews
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Comente o código, não a pessoa
&lt;/h3&gt;

&lt;p&gt;Trocar "você" por "esse método" muda tudo, e não custa nada:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ "Você esqueceu de tratar o retorno nulo aqui."&lt;br&gt;
✅ "Esse retorno pode vir nulo quando o cliente não tem endereço — dá exception na linha de baixo."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mesma informação. Só que a segunda fala do código, e a primeira aponta o dedo.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Diga o porquê e o impacto
&lt;/h3&gt;

&lt;p&gt;Comentário sem motivo vira gosto pessoal — e ninguém aceita gosto pessoal de bom grado.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ "Não usa &lt;code&gt;all()&lt;/code&gt; aqui."&lt;br&gt;
✅ "Esse &lt;code&gt;all()&lt;/code&gt; carrega a tabela inteira na memória. Em produção são uns 800 mil registros, o job vai estourar o limite. &lt;code&gt;chunkById()&lt;/code&gt; resolve."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A segunda versão não precisa de autoridade nenhuma pra convencer. O argumento se defende sozinho, e a pessoa aprende algo em vez de só obedecer.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Separe o que bloqueia do que é preferência
&lt;/h3&gt;

&lt;p&gt;Essa é a que mais melhora review em time, e é a mais fácil de adotar. Prefixo no comentário:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;bloqueante:&lt;/code&gt;&lt;/strong&gt; — não pode subir assim (bug, falha de segurança, quebra de contrato)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sugestão:&lt;/code&gt;&lt;/strong&gt; — melhoraria, mas é decisão sua&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;nit:&lt;/code&gt;&lt;/strong&gt; — implicância mínima, pode ignorar sem culpa&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;dúvida:&lt;/code&gt;&lt;/strong&gt; — eu não entendi, me explica&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sem isso, todo comentário pesa igual. Quem recebe dez apontamentos não sabe se tem um problema sério ou dez preferências, e trata tudo como reprovação.&lt;/p&gt;

&lt;p&gt;Com prefixo, "nit: acho que esse nome ficaria melhor no plural" é lido em dois segundos e resolvido em cinco.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Pergunta de verdade, não pergunta retórica
&lt;/h3&gt;

&lt;p&gt;Existe uma diferença enorme entre curiosidade e ironia disfarçada de pergunta:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ "Tem certeza que isso funciona?"&lt;br&gt;
❌ "Por que você fez desse jeito?"&lt;br&gt;
✅ "Fiquei em dúvida no caso do cliente sem contrato ativo — esse &lt;code&gt;where&lt;/code&gt; cobre isso? Posso ter perdido alguma coisa."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As duas primeiras são acusação com ponto de interrogação no fim. Todo mundo sente.&lt;/p&gt;

&lt;p&gt;E tem um bônus real na terceira: &lt;strong&gt;às vezes quem não entendeu foi você&lt;/strong&gt;. Manter a abertura de "posso ter perdido alguma coisa" já me salvou de várias sugestões erradas ditas com confiança demais.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Sugira o código, não só o problema
&lt;/h3&gt;

&lt;p&gt;O GitHub e o GitLab têm bloco de sugestão. Usa. Escrever a alternativa custa trinta segundos e muda a natureza do comentário: em vez de tarefa, vira ajuda.&lt;/p&gt;

&lt;p&gt;E força você a conferir se a sua ideia realmente funciona ali. Já desisti de uns bons comentários no meio da escrita da sugestão. 😅&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Comente o que tá bom também
&lt;/h3&gt;

&lt;p&gt;Não é bajulação nem "elogio sanduíche". É &lt;strong&gt;calibragem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Se você só aparece pra apontar problema, seus comentários viram sinal de perigo — a pessoa vê seu nome na notificação e já se contrai. Um "esse teste do cenário de borda ficou ótimo, eu não tinha pensado nesse caso" custa dez segundos e muda como todo o resto é lido.&lt;/p&gt;

&lt;p&gt;E tem um efeito colateral bom: elogiar o que você quer ver mais espalha padrão muito mais rápido do que criticar o que você não quer.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Passou de três idas e voltas, sai do texto
&lt;/h3&gt;

&lt;p&gt;Se o mesmo ponto já teve três rodadas de comentário, o problema não é mais técnico. Ou vocês estão discutindo coisas diferentes, ou tem contexto que não cabe em caixa de texto.&lt;/p&gt;

&lt;p&gt;Cinco minutos de call resolvem o que dez comentários não resolvem — e depois alguém registra no PR o que ficou combinado.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pegadinha: review de gosto pessoal
&lt;/h2&gt;

&lt;p&gt;Metade dos comentários azedos do mundo são sobre coisa que máquina deveria resolver.&lt;/p&gt;

&lt;p&gt;Aspas simples ou duplas, quebra de linha, ordem dos imports, espaço antes da chave. Se isso aparece em review, o problema não é a pessoa — é que falta &lt;strong&gt;Pint&lt;/strong&gt; (ou Pint + PHPStan) no pipeline.&lt;/p&gt;

&lt;p&gt;Configura uma vez, o robô aponta, ninguém se ofende com robô. E aí o review humano fica livre pro que importa: regra de negócio, caso de borda, nome que não descreve o que faz, teste que não testa nada.&lt;/p&gt;

&lt;p&gt;Regra que eu tento seguir: &lt;strong&gt;se um linter poderia ter dito, um humano não deveria dizer.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Do outro lado: recebendo review
&lt;/h2&gt;

&lt;p&gt;Vale dizer, porque é mão dupla.&lt;/p&gt;

&lt;p&gt;Todo comentário é sobre o código. Mesmo o mal escrito — quem escreveu provavelmente estava com pressa entre duas reuniões, não com raiva de você. Se um comentário te irritou, espera vinte minutos antes de responder. E se foi grosseiro mesmo, dá pra falar direto: "esse comentário soou meio seco, o que você quis dizer?". Quase sempre a resposta é "ih, desculpa, escrevi correndo".&lt;/p&gt;

&lt;p&gt;E quando discordar, discorda com argumento. Review não é ordem, é conversa — quem revisa também erra.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bônus: combinem uma página de regras
&lt;/h2&gt;

&lt;p&gt;Meia hora de conversa e um documento de uma página resolvem 80% do atrito: o que é bloqueante de verdade, em quanto tempo se espera resposta num PR, tamanho máximo de PR (o maior favor que você faz pro revisor é o PR pequeno), quem pode aprovar e os prefixos de comentário.&lt;/p&gt;

&lt;p&gt;Não é burocracia. É parar de negociar as mesmas coisas em todo PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de você fechar a aba
&lt;/h2&gt;

&lt;p&gt;Code review é o único momento em que o time inteiro conversa sobre código de verdade. É onde padrão se espalha, onde gente nova aprende rápido e onde bug morre barato.&lt;/p&gt;

&lt;p&gt;E é também onde relação se desgasta silenciosamente, um comentário seco de cada vez, até alguém começar a aprovar tudo só pra evitar a conversa. Aí acabou o review — sobrou o carimbo.&lt;/p&gt;

&lt;p&gt;Trinta segundos a mais escrevendo o comentário é o melhor investimento de tempo que existe no processo.&lt;/p&gt;

&lt;p&gt;Agora conta: &lt;strong&gt;qual o comentário mais babaca que você já recebeu num PR?&lt;/strong&gt; Ou — se tiver coragem, e eu tenho — qual foi o mais babaca que você já &lt;em&gt;escreveu&lt;/em&gt;? Eu abro os trabalhos: já escrevi um "isso não faz sentido" às 19h de sexta e passei o fim de semana inteiro me arrependendo. 🙈&lt;/p&gt;




</description>
      <category>career</category>
      <category>cleancode</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Mastering Advanced Server-Side Caching Patterns in Next.js</title>
      <dc:creator>Tamiz Uddin</dc:creator>
      <pubDate>Tue, 11 Aug 2026 18:01:08 +0000</pubDate>
      <link>https://dev.to/tamizuddin/mastering-advanced-server-side-caching-patterns-in-nextjs-3h68</link>
      <guid>https://dev.to/tamizuddin/mastering-advanced-server-side-caching-patterns-in-nextjs-3h68</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://tamiz.pro/insights/advanced-server-side-caching-nextjs-7513" rel="noopener noreferrer"&gt;tamiz.pro&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next.js, a robust React framework, has significantly evolved its caching mechanisms, moving beyond simple client-side strategies to embrace powerful server-side patterns. This deep-dive explores the intricacies of advanced server-side caching in Next.js, focusing on how developers can leverage Data Cache, Full Route Cache, and custom memoization techniques to build highly performant and scalable applications.&lt;/p&gt;

&lt;p&gt;Optimizing application performance is a continuous endeavor. In the context of server-rendered or statically generated applications like those built with Next.js, efficient caching on the server-side can drastically reduce response times, lower database load, and improve overall user experience. We'll dissect the various layers of caching available and provide practical insights into their implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1. Understanding Next.js Caching Fundamentals&lt;/li&gt;
&lt;li&gt;
2. Next.js Data Cache: &lt;code&gt;fetch&lt;/code&gt; and &lt;code&gt;revalidate&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;2.1. Automatic Caching with &lt;code&gt;fetch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;2.2. On-Demand Revalidation&lt;/li&gt;
&lt;li&gt;2.3. Time-Based Revalidation&lt;/li&gt;
&lt;li&gt;2.4. &lt;code&gt;unstable_cache&lt;/code&gt; for Non-&lt;code&gt;fetch&lt;/code&gt; Operations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
3. Full Route Cache: &lt;code&gt;next/cache&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;3.1. How Full Route Cache Works&lt;/li&gt;
&lt;li&gt;3.2. Cache Invalidation Strategies&lt;/li&gt;
&lt;li&gt;3.3. When to Opt-Out of Full Route Cache&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
4. Custom Server-Side Memoization and Caching

&lt;ul&gt;
&lt;li&gt;4.1. In-Memory Caching with &lt;code&gt;lru-cache&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;4.2. Leveraging Redis for Distributed Caching&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;5. Edge Caching with CDNs&lt;/li&gt;
&lt;li&gt;6. Best Practices and Considerations&lt;/li&gt;
&lt;li&gt;7. Frequently Asked Questions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Understanding Next.js Caching Fundamentals
&lt;/h2&gt;

&lt;p&gt;Next.js provides a sophisticated caching system that operates on both the client and server sides. While client-side caching (browser cache, service workers) is crucial for static assets, server-side caching is paramount for dynamic data and server-rendered content. The framework's caching mechanisms are designed to work seamlessly across different deployment targets, from Vercel's Edge Network to self-hosted Node.js servers.&lt;/p&gt;

&lt;p&gt;The core idea behind server-side caching in Next.js is to avoid re-fetching or re-computing data that hasn't changed, thereby serving content faster and reducing the load on upstream services (databases, APIs).&lt;/p&gt;

&lt;p&gt;Key server-side caching layers in Next.js include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Data Cache:&lt;/strong&gt; Primarily managed by the native &lt;code&gt;fetch&lt;/code&gt; API, it caches responses from HTTP requests. This cache is persistent across requests and deployments.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Full Route Cache:&lt;/strong&gt; Caches the entire rendered HTML of a route, along with its static assets. This is highly effective for routes that don't change frequently.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Custom Caching:&lt;/strong&gt; Implementing your own caching logic using libraries like &lt;code&gt;lru-cache&lt;/code&gt; or external services like Redis for fine-grained control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding how these layers interact and when to apply each is crucial for building high-performance Next.js applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Next.js Data Cache: &lt;code&gt;fetch&lt;/code&gt; and &lt;code&gt;revalidate&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The Next.js Data Cache is a powerful feature that automatically caches the results of &lt;code&gt;fetch&lt;/code&gt; requests on the server-side. This cache is persistent and can be revalidated on demand or after a certain time.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1. Automatic Caching with &lt;code&gt;fetch&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When you use the native &lt;code&gt;fetch&lt;/code&gt; API within &lt;code&gt;getServerSideProps&lt;/code&gt;, &lt;code&gt;getStaticProps&lt;/code&gt;, Server Components, or Route Handlers, Next.js automatically caches the data. This is a significant improvement, as it offloads the burden of manual caching.&lt;/p&gt;

&lt;p&gt;Consider a simple data fetch in a Server Component:&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="c1"&gt;// app/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPosts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// This fetch request will be automatically cached by Next.js&lt;/span&gt;
  &lt;span class="c1"&gt;// The cache key is derived from the URL and request options.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://jsonplaceholder.typicode.com/posts&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="c1"&gt;// cache: 'force-cache' is the default for static generation (SSG)&lt;/span&gt;
    &lt;span class="c1"&gt;// For SSR, default is 'no-store' if not explicitly set.&lt;/span&gt;
    &lt;span class="c1"&gt;// Here, we explicitly opt-in for caching.&lt;/span&gt;
    &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Revalidate every hour&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to fetch data&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&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;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPosts&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;main&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="p"&gt;&amp;gt;&lt;/span&gt;Latest Posts&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;ul&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;posts&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;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;post&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="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&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;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;main&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;In this example, the &lt;code&gt;fetch&lt;/code&gt; request for posts will be cached. Subsequent requests to &lt;code&gt;HomePage&lt;/code&gt; within the revalidation period will hit the cache, avoiding an external API call.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2. On-Demand Revalidation
&lt;/h3&gt;

&lt;p&gt;On-demand revalidation allows you to purge specific cached data when external data changes. This is critical for ensuring data freshness without sacrificing performance.&lt;/p&gt;

&lt;p&gt;You can revalidate data using the &lt;code&gt;revalidatePath&lt;/code&gt; or &lt;code&gt;revalidateTag&lt;/code&gt; functions from &lt;code&gt;next/cache&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, tag your &lt;code&gt;fetch&lt;/code&gt; requests:&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="c1"&gt;// lib/data.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getProduct&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="kr"&gt;string&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/products/&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="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tags&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="s1"&gt;products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`product-&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="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Assign tags to the cache entry&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to fetch product&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getProducts&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com/products&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;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tags&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="s1"&gt;products&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="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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to fetch products&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;Then, create a Route Handler (or API Route) to trigger revalidation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/revalidate/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;revalidatePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;revalidateTag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&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;NextRequest&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;secret&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;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secret&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;path&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;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&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;tag&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;nextUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag&lt;/span&gt;&lt;span class="dl"&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;secret&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;MY_SECRET_TOKEN&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="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid secret&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&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;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;revalidatePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;revalidated&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="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;path&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;tag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;revalidateTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;revalidated&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="na"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;tag&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="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Must provide path or tag&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&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;To trigger revalidation, you would send a request to &lt;code&gt;YOUR_APP_URL/api/revalidate?secret=YOUR_SECRET_TOKEN&amp;amp;tag=products&lt;/code&gt; or &lt;code&gt;YOUR_APP_URL/api/revalidate?secret=YOUR_SECRET_TOKEN&amp;amp;path=/products&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3. Time-Based Revalidation
&lt;/h3&gt;

&lt;p&gt;Time-based revalidation (Incremental Static Regeneration or ISR) is configured using the &lt;code&gt;revalidate&lt;/code&gt; option in &lt;code&gt;fetch&lt;/code&gt; or &lt;code&gt;getStaticProps&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;fetch&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In a Server Component or a data fetching function&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com/data&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;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Revalidate data every 60 seconds&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;getStaticProps&lt;/code&gt; (Pages Router):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/products/[id].tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticProps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://api.example.com/products/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&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="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;product&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="c1"&gt;// In-seconds&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;h3&gt;
  
  
  2.4. &lt;code&gt;unstable_cache&lt;/code&gt; for Non-&lt;code&gt;fetch&lt;/code&gt; Operations
&lt;/h3&gt;

&lt;p&gt;While &lt;code&gt;fetch&lt;/code&gt; handles caching for HTTP requests, what about data fetched from databases, file systems, or other non-HTTP sources? Next.js provides &lt;code&gt;unstable_cache&lt;/code&gt; (which will eventually be stabilized) to cache the results of any data-fetching function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/db.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;server-only&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;unstable_cache&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Simulate a database call&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchUserFromDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&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;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="s2"&gt;`Fetching user &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; from DB...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Simulate network delay&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`User &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@example.com`&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;unstable_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;fetchUserFromDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&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="s1"&gt;user-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Key parts for cache invalidation&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tags&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="s1"&gt;users&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="c1"&gt;// Tags for on-demand revalidation&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// In a Server Component&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="nx"&gt;Profile&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;unstable_cache&lt;/code&gt; takes three arguments:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;&lt;code&gt;fn&lt;/code&gt;&lt;/strong&gt;: The asynchronous function whose result you want to cache.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;&lt;code&gt;keyParts&lt;/code&gt;&lt;/strong&gt;: An array of strings used to create a unique cache key. If any part changes, the cache is busted.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;&lt;code&gt;options&lt;/code&gt;&lt;/strong&gt;: An object containing &lt;code&gt;revalidate&lt;/code&gt; (time-based) or &lt;code&gt;tags&lt;/code&gt; (on-demand revalidation) similar to &lt;code&gt;fetch&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This provides a universal caching mechanism for any server-side data fetching.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Full Route Cache: &lt;code&gt;next/cache&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The Full Route Cache (also known as the HTTP cache for routes) works at a higher level than the Data Cache. It caches the entire rendered output of a route (HTML, CSS, JS) at the edge or on the server. This is the fastest form of caching for complete pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1. How Full Route Cache Works
&lt;/h3&gt;

&lt;p&gt;Next.js leverages HTTP caching headers (&lt;code&gt;Cache-Control&lt;/code&gt;) to instruct browsers and CDNs (like Vercel's Edge Network) on how to cache entire routes. When a request comes in for a cached route, the CDN can serve the content directly without ever hitting your Next.js application server.&lt;/p&gt;

&lt;p&gt;By default, Next.js implements intelligent caching strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Static Routes:&lt;/strong&gt; Routes generated using &lt;code&gt;getStaticProps&lt;/code&gt; (Pages Router) or fully static Server Components (App Router) are highly cacheable, often with &lt;code&gt;public, max-age=31536000, immutable&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Dynamic Routes with &lt;code&gt;revalidate&lt;/code&gt;:&lt;/strong&gt; Routes using &lt;code&gt;getStaticProps&lt;/code&gt; with &lt;code&gt;revalidate&lt;/code&gt; or &lt;code&gt;fetch&lt;/code&gt; with &lt;code&gt;next.revalidate&lt;/code&gt; will have appropriate &lt;code&gt;Cache-Control&lt;/code&gt; headers (e.g., &lt;code&gt;s-maxage=60, stale-while-revalidate&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Dynamic Routes without &lt;code&gt;revalidate&lt;/code&gt; (SSR):&lt;/strong&gt; Routes rendered server-side without explicit revalidation often have &lt;code&gt;Cache-Control: private, no-cache, no-store, must-revalidate&lt;/code&gt; to prevent caching of potentially user-specific content.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.2. Cache Invalidation Strategies
&lt;/h3&gt;

&lt;p&gt;Full Route Cache invalidation typically happens through two primary methods:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Time-Based Revalidation:&lt;/strong&gt; As discussed with &lt;code&gt;revalidate&lt;/code&gt; in &lt;code&gt;fetch&lt;/code&gt; or &lt;code&gt;getStaticProps&lt;/code&gt;. The &lt;code&gt;s-maxage&lt;/code&gt; (shared cache max-age) header controls CDN caching duration.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;On-Demand Revalidation:&lt;/strong&gt; Using &lt;code&gt;revalidatePath&lt;/code&gt; and &lt;code&gt;revalidateTag&lt;/code&gt; not only invalidates the Data Cache but also signals the Next.js runtime (especially on Vercel) to purge the Full Route Cache for the affected paths or routes that depend on the invalidated tags. This ensures that the next request fetches fresh data and re-renders the page.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3.3. When to Opt-Out of Full Route Cache
&lt;/h3&gt;

&lt;p&gt;While highly beneficial, there are scenarios where you might want to bypass or disable the Full Route Cache for specific requests or pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Highly Personalized Content:&lt;/strong&gt; Pages that display user-specific data (e.g., a dashboard, shopping cart) should generally not be cached at the edge or publicly. Next.js typically handles this by default for &lt;code&gt;getServerSideProps&lt;/code&gt; or dynamic Server Components without explicit &lt;code&gt;revalidate&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Real-time Data:&lt;/strong&gt; If data changes extremely rapidly and needs to be reflected instantly (e.g., stock tickers), aggressive caching might be counterproductive. You might rely on client-side polling/websockets for such scenarios.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;User-Generated Content (pre-moderation):&lt;/strong&gt; For content that requires moderation before public display, you might want to ensure the latest version is always fetched for moderators.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To explicitly opt-out of caching for a route in the App Router, you can use &lt;code&gt;export const dynamic = 'force-dynamic'&lt;/code&gt; in a layout or page, or set &lt;code&gt;cache: 'no-store'&lt;/code&gt; in your &lt;code&gt;fetch&lt;/code&gt; requests. For the Pages Router, using &lt;code&gt;getServerSideProps&lt;/code&gt; without &lt;code&gt;revalidate&lt;/code&gt; implicitly prevents static caching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/dashboard/page.tsx&lt;/span&gt;
&lt;span class="c1"&gt;// This page will always be rendered dynamically on the server and not cached by the Full Route Cache&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;dynamic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;force-dynamic&lt;/span&gt;&lt;span class="dl"&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DashboardPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... fetch and display user-specific data ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Custom Server-Side Memoization and Caching
&lt;/h2&gt;

&lt;p&gt;Beyond Next.js's built-in caching, there are situations where you need more granular control or need to cache data that doesn't fit neatly into the &lt;code&gt;fetch&lt;/code&gt; or &lt;code&gt;unstable_cache&lt;/code&gt; paradigms. This is where custom server-side memoization and caching come into play.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1. In-Memory Caching with &lt;code&gt;lru-cache&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;For short-lived, frequently accessed data within a single Node.js process, an in-memory cache like &lt;code&gt;lru-cache&lt;/code&gt; is an excellent choice. It automatically evicts the least recently used items when the cache reaches its maximum size.&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;lru-cache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/lru-cache-service.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LRUCache&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lru-cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CacheValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Max number of items in cache&lt;/span&gt;
  &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&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;// 5 minutes time-to-live for cache entries&lt;/span&gt;
  &lt;span class="na"&gt;updateAgeOnGet&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="c1"&gt;// Update age of item on get&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;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;LRUCache&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CacheValue&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;options&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getCachedData&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetcher&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;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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;cached&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;))&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="s2"&gt;`Cache hit for key: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&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="s2"&gt;`Cache miss for key: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, fetching...`&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;now&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;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example usage in a Server Component or Route Handler&lt;/span&gt;
&lt;span class="c1"&gt;// app/api/products/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getCachedData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../lib/lru-cache-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchExpensiveProductData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Simulate an expensive operation or database query&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="s2"&gt;`Fetching expensive data for product &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Expensive Product &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&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;Request&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt; &lt;span class="p"&gt;}&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;URL&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;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;productId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&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;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getCachedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`product-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;fetchExpensiveProductData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;product&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;strong&gt;Caveat:&lt;/strong&gt; &lt;code&gt;lru-cache&lt;/code&gt; is process-specific. In a serverless environment or with multiple Node.js instances, each instance will have its own cache, leading to potential cache misses across instances.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2. Leveraging Redis for Distributed Caching
&lt;/h3&gt;

&lt;p&gt;For distributed environments, serverless functions, or when you need a shared, persistent cache across multiple instances, an external caching store like Redis is essential.&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;redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/redis-service.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis&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;redisClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;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;REDIS_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis://localhost:6379&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;redisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&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;err&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;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;Redis Client Error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;connectRedis&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;redisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isReady&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;redisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&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;Connected to Redis&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getOrSetCache&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetcher&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;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;ttlSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;await&lt;/span&gt; &lt;span class="nf"&gt;connectRedis&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;cachedData&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;redisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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;cachedData&lt;/span&gt;&lt;span class="p"&gt;)&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="s2"&gt;`Redis Cache hit for key: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&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="s2"&gt;`Redis Cache miss for key: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, fetching...`&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;freshData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetcher&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;redisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ttlSeconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;freshData&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;freshData&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;invalidateCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="nf"&gt;connectRedis&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;redisClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;del&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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="s2"&gt;`Redis Cache invalidated for key: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example usage in a Server Component or Route Handler&lt;/span&gt;
&lt;span class="c1"&gt;// app/api/long-computation/route.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getOrSetCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invalidateCache&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../lib/redis-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;performLongComputation&lt;/span&gt;&lt;span class="p"&gt;()&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;Performing long computation...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Simulate 3-second computation&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;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Computed Value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GET&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;Request&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getOrSetCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-long-computation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;performLongComputation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example for invalidation&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&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;Request&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="nf"&gt;invalidateCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-long-computation&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;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache invalidated&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;Redis offers robust features like persistence, replication, and pub/sub, making it suitable for complex caching scenarios. Remember to secure your Redis instance and manage connections efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Edge Caching with CDNs
&lt;/h2&gt;

&lt;p&gt;For maximum performance, combine Next.js's built-in caching with a Content Delivery Network (CDN) like Vercel's Edge Network, Cloudflare, or AWS CloudFront. CDNs cache static assets and entire HTML pages at geographically distributed points of presence (PoPs), serving content closer to users and reducing latency.&lt;/p&gt;

&lt;p&gt;When deploying to Vercel, much of the edge caching is automatically managed based on your Next.js build output and &lt;code&gt;Cache-Control&lt;/code&gt; headers. For self-hosting, you'll need to configure your CDN to respect these headers and potentially add custom rules for caching and invalidation.&lt;/p&gt;

&lt;p&gt;Key aspects of CDN caching:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Static Assets:&lt;/strong&gt; JavaScript bundles, CSS, images, fonts are aggressively cached by CDNs.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;HTML Pages:&lt;/strong&gt; Pages generated via SSG or ISR are cached at the edge, providing near-instant load times for subsequent visits.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;Cache-Control&lt;/code&gt; Headers:&lt;/strong&gt; Next.js sets appropriate &lt;code&gt;Cache-Control&lt;/code&gt; headers. &lt;code&gt;s-maxage&lt;/code&gt; is particularly important for instructing shared caches (like CDNs) how long to store a resource.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Stale-While-Revalidate:&lt;/strong&gt; This HTTP header strategy allows CDNs to serve stale content immediately while asynchronously revalidating it in the background, providing a great balance between freshness and speed.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Example Cache-Control header for a page with ISR (revalidate: 60)
Cache-Control: public, s-maxage=60, stale-while-revalidate=59
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This header tells the CDN to serve the cached content for 60 seconds (&lt;code&gt;s-maxage=60&lt;/code&gt;). If a request comes in after 60 seconds but before &lt;code&gt;stale-while-revalidate&lt;/code&gt; expires (e.g., 59 seconds after &lt;code&gt;s-maxage&lt;/code&gt;), the CDN will serve the stale content immediately and trigger a revalidation request to the origin in the background. Once the origin responds, the cache is updated.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Best Practices and Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Granularity:&lt;/strong&gt; Cache at the most granular level possible. Cache individual data fetches rather than entire pages if only parts of the page change frequently.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Invalidation Strategy:&lt;/strong&gt; Plan your cache invalidation strategy carefully. On-demand revalidation is often preferred for dynamic data to ensure freshness, while time-based revalidation (ISR) is great for content that updates periodically.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cache Keys:&lt;/strong&gt; Design clear and consistent cache keys. For custom caches, ensure keys are unique and reflect the data they represent.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Server-Side vs. Client-Side:&lt;/strong&gt; Distinguish between server-side caching (for performance and reduced origin load) and client-side caching (for faster subsequent user visits and offline capabilities). Both are important.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;no-store&lt;/code&gt; vs. &lt;code&gt;no-cache&lt;/code&gt;:&lt;/strong&gt; Understand the difference. &lt;code&gt;no-store&lt;/code&gt; means the response should never be stored by any cache. &lt;code&gt;no-cache&lt;/code&gt; means the cache must re-validate with the origin server before serving a cached copy.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Security:&lt;/strong&gt; Be mindful of caching sensitive user data. Use &lt;code&gt;private&lt;/code&gt; cache control or &lt;code&gt;no-store&lt;/code&gt; for authenticated and personalized content.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Monitoring:&lt;/strong&gt; Monitor your cache hit rates and performance metrics. This helps identify areas for improvement and diagnose caching issues.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Local Development:&lt;/strong&gt; Caching behaves differently in development vs. production. Be aware of &lt;code&gt;next dev&lt;/code&gt; not always reflecting production caching behavior accurately. Use &lt;code&gt;next build&lt;/code&gt; and &lt;code&gt;next start&lt;/code&gt; to test production caching locally.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;'use server'&lt;/code&gt; and Caching:&lt;/strong&gt; Server Actions (functions marked with &lt;code&gt;'use server'&lt;/code&gt;) can also benefit from caching. If they call data-fetching functions that are cached (e.g., &lt;code&gt;fetch&lt;/code&gt; with &lt;code&gt;revalidate&lt;/code&gt; or &lt;code&gt;unstable_cache&lt;/code&gt;), those results will be cached. However, the action itself is a mutation, so ensure you revalidate any affected data after a successful action.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Q: What's the difference between &lt;code&gt;revalidatePath&lt;/code&gt; and &lt;code&gt;revalidateTag&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;A: &lt;code&gt;revalidatePath&lt;/code&gt; invalidates the cache for a specific path (e.g., &lt;code&gt;/products/1&lt;/code&gt;). This is useful when you know exactly which page needs to be updated. &lt;code&gt;revalidateTag&lt;/code&gt; invalidates all &lt;code&gt;fetch&lt;/code&gt; requests (and &lt;code&gt;unstable_cache&lt;/code&gt; calls) that were associated with a specific tag (e.g., &lt;code&gt;products&lt;/code&gt;). This is more powerful for batch invalidation when multiple pages or components depend on the same dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: Can I use server-side caching with &lt;code&gt;getServerSideProps&lt;/code&gt; in the Pages Router?
&lt;/h3&gt;

&lt;p&gt;A: Yes, &lt;code&gt;getServerSideProps&lt;/code&gt; does not participate in &lt;code&gt;getStaticProps&lt;/code&gt;-style static generation or ISR. However, you can still use &lt;code&gt;fetch&lt;/code&gt; with &lt;code&gt;next: { revalidate: N }&lt;/code&gt; inside &lt;code&gt;getServerSideProps&lt;/code&gt; to cache individual data fetches. The overall HTML response of the &lt;code&gt;getServerSideProps&lt;/code&gt; page won't be cached at the edge by Next.js by default, but you can configure your CDN to cache it if &lt;code&gt;Cache-Control&lt;/code&gt; headers allow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: How does caching work with dynamic routes like &lt;code&gt;/blog/[slug]&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;A: For dynamic routes, if you're using &lt;code&gt;getStaticProps&lt;/code&gt; with &lt;code&gt;getStaticPaths&lt;/code&gt; (Pages Router) or a &lt;code&gt;generateStaticParams&lt;/code&gt; function (App Router) combined with &lt;code&gt;revalidate&lt;/code&gt;, Next.js will pre-render and cache each unique page at build time or on demand. If you're using &lt;code&gt;fetch&lt;/code&gt; with &lt;code&gt;revalidate&lt;/code&gt; in a Server Component for a dynamic route, each unique path will have its data cached independently based on the &lt;code&gt;fetch&lt;/code&gt; options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: What are the implications of caching on SEO?
&lt;/h3&gt;

&lt;p&gt;A: Server-side caching, especially of full routes, generally has a positive impact on SEO. Faster page load times are a ranking factor, and cached content is served more quickly to search engine crawlers. Ensure your caching strategy doesn't accidentally serve stale content to crawlers for extended periods, especially for rapidly changing information. On-demand revalidation helps maintain freshness for SEO-critical content.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>javascript</category>
      <category>react</category>
      <category>server</category>
    </item>
    <item>
      <title>The 3 3D animation principles by RingCentral that make black-box AI feel tangible - Explained by Advids</title>
      <dc:creator>Harshal Patil</dc:creator>
      <pubDate>Tue, 11 Aug 2026 17:55:56 +0000</pubDate>
      <link>https://dev.to/advidsco/the-3-3d-animation-principles-by-ringcentral-that-make-black-box-ai-feel-tangible-explained-by-1ed5</link>
      <guid>https://dev.to/advidsco/the-3-3d-animation-principles-by-ringcentral-that-make-black-box-ai-feel-tangible-explained-by-1ed5</guid>
      <description>&lt;p&gt;"We've noticed a persistent hurdle in launching enterprise AI: users simply do not trust what they cannot see or spatialize. When artificial intelligence just operates silently in the background of a dashboard, contact center agents often feel displaced or confused rather than supported. RingCentral circumvents this in their RingCX rollout by utilizing depth, layering, and spatial motion to make their RingSense AI feel like a physical, observable entity in the workspace.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/5KuHUpDJwAw"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualizing the invisible algorithm
&lt;/h2&gt;

&lt;p&gt;The core design assumption in many platforms is that an AI should just be a frictionless output text box. &lt;strong&gt;The truth is that AI needs a spatial presence and physical boundaries in the UI to build real user trust.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does background AI fail to gain agent trust?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Agents ignore flat text insights that appear without a clear origin or visual hierarchy.&lt;/li&gt;
&lt;li&gt;Supervisors struggle to intervene when real-time sentiment shifts lack visual urgency or motion.&lt;/li&gt;
&lt;li&gt;Coaching plans generated instantly feel overly automated and impersonal when dumped into a standard, flat data table.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the Video Actually Does
&lt;/h2&gt;

&lt;p&gt;Notice how the RingCX interface handles real-time agent alerts during the video. Concentric, radar-like ripples animate behind the typography, signaling visually that the AI is actively ""listening"" and processing the live voice channel. When an agent needs help with an auto claim, the ""Insurance 101"" insight doesn't just appear—it pops up on an elevated card with a soft drop shadow. &lt;/p&gt;

&lt;p&gt;This deliberate use of the Z-axis separates the AI's real-time suggestions from the flat CRM data beneath it. A glowing AI spark icon anchors these floating elements, giving the algorithm a distinct visual footprint within the workflow. By treating the AI as an overlapping physical layer rather than embedded background text, the interface makes complex LLM operations feel tangible and predictable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI adoption isn't just about the underlying model's accuracy—it's about giving the algorithm a tangible, observable presence in the user's environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;How are you handling the UI/UX challenge of surfacing proactive AI insights without overwhelming your existing operational dashboards?"&lt;/p&gt;

</description>
      <category>video</category>
      <category>animationprinciples</category>
      <category>videoproduction</category>
    </item>
  </channel>
</rss>
