Skip to content

Commit 83756ed

Browse files
committed
Merge branch 'main' into create-ui
2 parents ed2e519 + 1f0d77a commit 83756ed

File tree

3 files changed

+194
-0
lines changed

3 files changed

+194
-0
lines changed

media/brand.sketch

14.1 KB
Binary file not shown.
177 KB
Loading
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
---
2+
title: Directives Are Becoming the New Framework Lock In
3+
published: 2025-10-24
4+
authors:
5+
- Tanner Linsley
6+
---
7+
8+
![Directives Are Becoming the New Framework Lock In - A Quiet Problem in the JavaScript Ecosystem](/blog-assets/directives-the-new-framework-lock-in/header.png)
9+
10+
## A Quiet Problem in the JavaScript Ecosystem
11+
12+
For years, JavaScript has had exactly one meaningful directive, `"use strict"`. It is standardized, enforced by runtimes, and behaves the same in every environment. It represents a clear contract between the language, the engines, and developers.
13+
14+
But now we are watching a new trend emerge. Frameworks are inventing their own top level directives, `use client`, `use server`, `use cache`, `use workflow`, and more are appearing across the ecosystem. They look like language features. They sit where real language features sit. They affect how code is interpreted, bundled, and executed.
15+
16+
There is just one problem.
17+
18+
**They are not JavaScript.**
19+
20+
They are not standardized. Browsers do not understand them. They have no governing specification. And each framework is free to define its own meaning, its own rules, and its own edge cases.
21+
22+
This might feel harmless or ergonomic today, but it carries long term consequences for the ecosystem, consequences we have seen before.
23+
24+
---
25+
26+
### When Directives Look Like the Platform, Developers Treat Them Like the Platform
27+
28+
A directive at the top of a file looks authoritative. It gives the impression of being a language level truth, not a framework hint. That creates a perception problem:
29+
30+
- Developers assume directives are official
31+
- Ecosystems begin to treat them as a shared API surface
32+
- New learners struggle to distinguish JavaScript from framework magic
33+
- The boundary between platform and vendor blurs
34+
35+
We are already seeing confusion in the wild. Many developers now believe `use client` and `use server` are just how modern JavaScript works, unaware that they only exist inside specific build pipelines and server component semantics. That misunderstanding is a signal of a deeper issue.
36+
37+
---
38+
39+
### A Shared Syntax Without a Shared Spec Is a Fragile Foundation
40+
41+
Once multiple frameworks start adopting directives, we end up in the worst possible state:
42+
43+
| Category | Shared Syntax | Shared Contract | Result |
44+
| -------------------- | ------------- | --------------- | ---------------------- |
45+
| ECMAScript ||| Stable and universal |
46+
| Framework APIs ||| Isolated and fine |
47+
| Framework Directives ||| Confusing and unstable |
48+
49+
A shared surface area without a shared definition creates:
50+
51+
- Interpretation drift, each framework defines its own semantics
52+
- Portability issues, code that looks universal but is not
53+
- Tooling burden, bundlers, linters, and IDEs must guess or chase behavior
54+
- Platform friction, standards bodies get boxed in by ecosystem expectations
55+
56+
We already lived through this with decorators. TypeScript normalized a non standard semantics, the community built on top of it, then TC39 went in a different direction. Years of pain followed.
57+
58+
Why are we walking into the same trap again?
59+
60+
---
61+
62+
### “Isn’t this just a Babel plugin/macro with different syntax?”
63+
64+
Functionally, yes — both directives and custom transforms can change behavior at compile time. The issue isn’t capability; it’s surface and optics.
65+
66+
- Directives look like the platform. No import, no owner, no explicit source. They signal “this is JavaScript.”
67+
- APIs/macros point to an owner. Imports provide provenance, versioning, and discoverability.
68+
69+
At best, a directive is equivalent to calling a global, importless function like `window.useCache()` at the top of your file. That’s exactly why it’s risky: it hides the provider and smuggles framework semantics into what looks like language.
70+
71+
Examples:
72+
73+
```js
74+
'use cache'
75+
const fn = () => 'value'
76+
```
77+
78+
```js
79+
// explicit API (imported, ownable, discoverable)
80+
import { createServerFn } from '@acme/runtime'
81+
export const fn = createServerFn(() => 'value')
82+
```
83+
84+
```js
85+
// global magic (importless, hidden provider)
86+
window.useCache()
87+
const fn = () => 'value'
88+
```
89+
90+
Why this matters:
91+
92+
- Ownership and provenance: imports tell you who provides the behavior; directives do not.
93+
- Tooling ergonomics: APIs live in package space; directives require ecosystem-wide special-casing.
94+
- Portability and migration: replacing an imported API is straightforward; unwinding directive semantics across files is costly and ambiguous.
95+
- Education and expectations: directives blur the platform boundary; APIs make the boundary explicit.
96+
97+
So while a custom Babel plugin or macro can implement the same underlying feature, the import-based API keeps it clearly in framework space. Directives move that same behavior into what looks like language space, which is the core concern of this post.
98+
99+
### “Does namespacing fix it?” (e.g., "use next.js cache")
100+
101+
Namespacing helps human discoverability, but it doesn’t address the core problems:
102+
103+
- It still looks like the platform. A top-level string literal implies language, not library.
104+
- It still lacks provenance and versioning at the module level. Imports encode both; strings do not.
105+
- It still requires special-casing across the toolchain (bundlers, linters, IDEs), rather than leveraging normal import resolution.
106+
- It still encourages pseudo-standardization of syntax without a spec, just with vendor prefixes.
107+
- It still increases migration cost compared to swapping an imported API.
108+
109+
Examples:
110+
111+
```js
112+
'use next.js cache'
113+
const fn = () => 'value'
114+
```
115+
116+
```js
117+
// explicit, ownable API with provenance and versioning
118+
import { cache } from 'next/cache'
119+
export const fn = cache(() => 'value')
120+
```
121+
122+
If the goal is provenance, imports already solve that cleanly and work with today’s ecosystem. If the goal is a shared cross-framework primitive, that needs a real spec, not vendor strings that look like syntax.
123+
124+
---
125+
126+
### Directives Create an Ecosystem Arms Race
127+
128+
Once directives become a competitive surface, the incentives shift:
129+
130+
1. One vendor ships a new directive
131+
2. It becomes a marketing feature
132+
3. Developers expect it everywhere
133+
4. Other frameworks feel forced to copy it
134+
5. The pseudo standard spreads without a spec
135+
136+
This is how you get:
137+
138+
```tsx
139+
'use server'
140+
'use client'
141+
'use cache'
142+
'use cache:remote'
143+
'use workflow'
144+
'use streaming'
145+
'use edge'
146+
```
147+
148+
Even durable tasks, caching strategies, and execution locations are now being encoded as directives. These are runtime semantics, not syntax semantics. Encoding them as directives is a form of platform creep, an attempt to define how developers think about capability boundaries using what looks like language grammar.
149+
150+
That is not harmless. That is direction setting outside the standards process.
151+
152+
---
153+
154+
### The Lock In Is Subtle, but Real
155+
156+
Even when there is no bad intent, directives create lock in by design:
157+
158+
- Mental lock in, developers form muscle memory around a vendor's directive semantics
159+
- Tooling lock in, IDEs, bundlers, and compilers must target a specific runtime
160+
- Code lock in, directives sit at the syntax level, making them costly to remove or migrate
161+
162+
Directives do not look proprietary, but they behave more proprietary than an API ever could, because they reshape the grammar of the ecosystem.
163+
164+
---
165+
166+
### If We Want Shared Primitives, We Should Collaborate, Not Fork the Language
167+
168+
There absolutely are real problems to solve:
169+
170+
- Server execution boundaries
171+
- Streaming and async workflows
172+
- Distributed runtime primitives
173+
- Durable tasks
174+
- Caching semantics
175+
176+
But those are problems for **APIs, capabilities, and future standards**, not for ungoverned pseudo syntax pushed through bundlers.
177+
178+
If multiple frameworks truly want shared primitives, the responsible path is:
179+
180+
- Collaborate on a cross framework spec
181+
- Propose primitives to TC39 when appropriate
182+
- Keep non standard features clearly scoped to API space, not language space
183+
184+
Directives should be rare, stable, and standardized, not multiplied by every vendor with a new idea.
185+
186+
---
187+
188+
### The Bottom Line
189+
190+
Framework directives might feel like DX magic today, but the current trend points toward a fractured future, JavaScript dialects defined not by standards, but by vendors.
191+
192+
We can do better.
193+
194+
If frameworks want to innovate, they should, but they should also clearly distinguish **framework behavior** from **platform semantics**, instead of blurring that line for short term adoption. The health of the ecosystem depends on it.

0 commit comments

Comments
 (0)