Skip to content

Commit f340dc7

Browse files
committed
tone down the directive post
1 parent 0c3d6da commit f340dc7

File tree

4 files changed

+64
-50
lines changed

4 files changed

+64
-50
lines changed
119 KB
Loading
Binary file not shown.

src/blog/directives-the-new-framework-lock-in.md renamed to src/blog/directives-and-the-platform-boundary.md

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,36 @@
11
---
2-
title: Directives Are Becoming the New Framework Lock In
2+
title: Directives and the Platform Boundary
33
published: 2025-10-24
44
authors:
55
- Tanner Linsley
6+
description: A constructive look at framework directives, portability, and keeping a clear boundary between platform and library spaces.
67
---
78

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+
![Header Image](/blog-assets/directives-and-the-platform-boundary/header.png)
910

10-
## A Quiet Problem in the JavaScript Ecosystem
11+
## A Quiet Trend in the JavaScript Ecosystem
1112

1213
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.
1314

1415
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.
1516

16-
There is just one problem.
17+
There is an important distinction: these are not standardized JavaScript features. Runtimes don't understand them, there is no governing specification, and each framework is free to define its own meaning, rules, and edge cases.
1718

18-
**They are not JavaScript.**
19-
20-
They are not standardized. Runtimes don't 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.
19+
This can feel ergonomic today, but it also increases confusion, complicates debugging, and imposes costs on tooling and portability—patterns we’ve seen before.
2320

2421
---
2522

26-
### When Directives Look Like the Platform, Developers Treat Them Like the Platform
23+
### When directives look like the platform, developers treat them like the platform
2724

2825
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:
2926

3027
- Developers assume directives are official
3128
- Ecosystems begin to treat them as a shared API surface
3229
- New learners struggle to distinguish JavaScript from framework magic
3330
- The boundary between platform and vendor blurs
31+
- Debuggability suffers and tooling must special‑case behaviors
3432

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.
33+
We’ve already seen confusion. 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 signals a deeper issue.
3634

3735
---
3836

@@ -42,9 +40,9 @@ Some directives exist because multiple tools needed a single, simple coordinatio
4240

4341
That said, even these show the limits of directives once real-world needs appear. At scale, you often need parameters and policies that matter deeply to correctness and security: HTTP method, headers, middleware, auth context, tracing, caching behaviors, and more. Directives have no natural place to carry those options, which means they are frequently ignored, bolted on elsewhere, or re-encoded as new directive variants.
4442

45-
### The real offenders: option-laden directives and directive-adjacent APIs
43+
### Where directives start to strain: options and directive-adjacent APIs
4644

47-
When a directive immediately, or soon after creation, needs options or spawns siblings (e.g., `'use cache:remote'`) and helper calls like `cacheLife(...)`, that’s a strong signal the feature wants to be an API, not a string at the top of a file. If you know you need a function anyway, just use a function for all of it.
45+
When a directive immediately, or soon after creation, needs options or spawns siblings (e.g., `'use cache:remote'`) and helper calls like `cacheLife(...)`, that’s often a signal the feature wants to be an API, not a string at the top of a file. If you know you need a function anyway, just use a function for all of it.
4846

4947
Examples:
5048

@@ -67,20 +65,23 @@ And for server behavior where details matter:
6765
```js
6866
import { server } from '@acme/runtime'
6967

70-
export const action = server(async (req) => {
71-
return new Response('ok')
72-
}, {
73-
method: 'POST',
74-
headers: { 'x-foo': 'bar' },
75-
middleware: [requireAuth()],
76-
})
68+
export const action = server(
69+
async (req) => {
70+
return new Response('ok')
71+
},
72+
{
73+
method: 'POST',
74+
headers: { 'x-foo': 'bar' },
75+
middleware: [requireAuth()],
76+
}
77+
)
7778
```
7879

79-
APIs carry provenance (imports), versioning (packages), composition (functions), and testability. Directives don’t — and trying to smuggle options into them quickly becomes a design smell.
80+
APIs carry provenance (imports), versioning (packages), composition (functions), and testability. Directives typically don’t — and trying to encode options into them can quickly become a design smell.
8081

8182
---
8283

83-
### A Shared Syntax Without a Shared Spec Is a Fragile Foundation
84+
### Shared syntax without a shared spec can be a fragile foundation
8485

8586
Once multiple frameworks start adopting directives, we end up in the worst possible state:
8687

@@ -97,9 +98,7 @@ A shared surface area without a shared definition creates:
9798
- Tooling burden, bundlers, linters, and IDEs must guess or chase behavior
9899
- Platform friction, standards bodies get boxed in by ecosystem expectations
99100

100-
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.
101-
102-
Why are we walking into the same trap again?
101+
An example of where we've seen these struggles before is with decorators. TypeScript normalized a non standard semantics, the community built on top of it, then TC39 went in a different direction. This was and continues to be a painful migration for many.
103102

104103
---
105104

@@ -110,7 +109,7 @@ Functionally, yes — both directives and custom transforms can change behavior
110109
- Directives look like the platform. No import, no owner, no explicit source. They signal “this is JavaScript.”
111110
- APIs/macros point to an owner. Imports provide provenance, versioning, and discoverability.
112111

113-
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.
112+
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 moves framework semantics into what looks like language.
114113

115114
Examples:
116115

@@ -167,15 +166,15 @@ If the goal is provenance, imports already solve that cleanly and work with toda
167166

168167
---
169168

170-
### Directives Create an Ecosystem Arms Race
169+
### Directives can drive competitive dynamics
171170

172171
Once directives become a competitive surface, the incentives shift:
173172

174173
1. One vendor ships a new directive
175-
2. It becomes a marketing feature
174+
2. It becomes a visible feature
176175
3. Developers expect it everywhere
177-
4. Other frameworks feel forced to copy it
178-
5. The pseudo standard spreads without a spec
176+
4. Other frameworks feel pressure to adopt it
177+
5. The syntax spreads without a spec
179178

180179
This is how you get:
181180

@@ -189,25 +188,23 @@ This is how you get:
189188
'use edge'
190189
```
191190

192-
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.
193-
194-
That is not harmless. That is direction setting outside the standards process.
191+
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 sets direction outside the standards process and merits caution.
195192

196193
---
197194

198-
### The Lock In Is Subtle, but Real
195+
### Subtle forms of lock‑in can emerge
199196

200197
Even when there is no bad intent, directives create lock in by design:
201198

202199
- Mental lock in, developers form muscle memory around a vendor's directive semantics
203200
- Tooling lock in, IDEs, bundlers, and compilers must target a specific runtime
204201
- Code lock in, directives sit at the syntax level, making them costly to remove or migrate
205202

206-
Directives do not look proprietary, but they behave more proprietary than an API ever could, because they reshape the grammar of the ecosystem.
203+
Directives may not look proprietary, but they can behave more like proprietary features than an API would, because they reshape the grammar of the ecosystem.
207204

208205
---
209206

210-
### If We Want Shared Primitives, We Should Collaborate, Not Fork the Language
207+
### If we want shared primitives, we should collaborate on specs and APIs
211208

212209
There absolutely are real problems to solve:
213210

@@ -219,26 +216,26 @@ There absolutely are real problems to solve:
219216

220217
But those are problems for **APIs, capabilities, and future standards**, not for ungoverned pseudo syntax pushed through bundlers.
221218

222-
If multiple frameworks truly want shared primitives, the responsible path is:
219+
If multiple frameworks truly want shared primitives, a responsible path is:
223220

224221
- Collaborate on a cross framework spec
225222
- Propose primitives to TC39 when appropriate
226223
- Keep non standard features clearly scoped to API space, not language space
227224

228-
Directives should be rare, stable, and standardized, not multiplied by every vendor with a new idea.
225+
Directives should be rare, stable, and standardized—used judiciously rather than proliferating across vendors.
229226

230227
---
231228

232-
### This is not the JSX/virtual DOM moment
229+
### Why this differs from the JSX/virtual DOM moment
233230

234-
It’s tempting to compare criticism of directives to the early skepticism around React’s JSX or the virtual DOM. I get the sentiment, but the failure modes are different. JSX and the VDOM did not masquerade as language features; they came with explicit imports, provenance, and tooling boundaries. Directives, by contrast, live at the top-level of files and look like the platform, which creates ecosystem expectations and tooling burdens without a shared spec.
231+
It’s tempting to compare criticism of directives to the early skepticism around React’s JSX or the virtual DOM. The failure modes are different. JSX and the VDOM did not masquerade as language features; they came with explicit imports, provenance, and tooling boundaries. Directives, by contrast, live at the top-level of files and look like the platform, which creates ecosystem expectations and tooling burdens without a shared spec.
235232

236233
---
237234

238-
### The Bottom Line
235+
### The bottom line
239236

240-
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.
237+
Framework directives might feel like DX magic today, but the current trend risks a more fragmented future—dialects defined not by standards, but by tools.
241238

242-
We can do better.
239+
We can aim for clearer boundaries.
243240

244-
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.
241+
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. Clearer boundaries help the ecosystem.

src/routes/_libraries/blog.$.tsx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { Link, notFound, createFileRoute } from '@tanstack/react-router'
1+
import {
2+
Link,
3+
notFound,
4+
createFileRoute,
5+
redirect,
6+
} from '@tanstack/react-router'
27
import { seo } from '~/utils/seo'
38
import { Doc } from '~/components/Doc'
49
import { PostNotFound } from './blog'
@@ -11,13 +16,23 @@ import { DocContainer } from '~/components/DocContainer'
1116
import { setResponseHeaders } from '@tanstack/react-start/server'
1217
import { allPosts } from 'content-collections'
1318

19+
function handleRedirects(docsPath: string) {
20+
if (docsPath.includes('directives-the-new-framework-lock-in')) {
21+
throw redirect({
22+
href: '/blog/directives-and-the-platform-boundary',
23+
})
24+
}
25+
}
26+
1427
const fetchBlogPost = createServerFn({ method: 'GET' })
1528
.inputValidator(z.string().optional())
1629
.handler(async ({ data: docsPath }) => {
1730
if (!docsPath) {
1831
throw new Error('Invalid docs path')
1932
}
2033

34+
handleRedirects(docsPath)
35+
2136
const filePath = `src/blog/${docsPath}.md`
2237

2338
const post = allPosts.find((post) => post.slug === docsPath)
@@ -26,11 +41,13 @@ const fetchBlogPost = createServerFn({ method: 'GET' })
2641
throw notFound()
2742
}
2843

29-
setResponseHeaders({
30-
'cache-control': 'public, max-age=0, must-revalidate',
31-
'cdn-cache-control': 'max-age=300, stale-while-revalidate=300, durable',
32-
'Netlify-Vary': 'query=payload',
33-
})
44+
setResponseHeaders(
45+
new Headers({
46+
'cache-control': 'public, max-age=0, must-revalidate',
47+
'cdn-cache-control': 'max-age=300, stale-while-revalidate=300, durable',
48+
'Netlify-Vary': 'query=payload',
49+
})
50+
)
3451

3552
return {
3653
title: post.title,

0 commit comments

Comments
 (0)