feat: support proxying useSanityQuery requests#1286
Conversation
❌ Deploy Preview for nuxt-sanity-module failed. Why did it fail? →
|
0ba1f8f to
4dc067c
Compare
commit: |
4dc067c to
7d9ee0b
Compare
a92101c to
bb09f6b
Compare
| if (writeTimer) clearTimeout(writeTimer) | ||
| writeTimer = setTimeout(async () => { | ||
| try { | ||
| await writeFile(queriesFilePath, JSON.stringify(queryArr), 'utf8') |
There was a problem hiding this comment.
we should be able to use addServerTemplate so this becomes a virtual file and we don't need to read from disk
There was a problem hiding this comment.
Oh cool, I wasn't sure how to make this work with addServerTemplate so I'm all ears here!
Currently we only read from the disk at runtime in dev so we can have an up-to-date list, as we find queries incrementally using a Vite plugin during transform, which obviously processes files on demand. In prod, the Rollup plugin exposes a virtual module (#sanity-groq-queries) that reads that file at build time and inlines the contents, so there shouldn't be any runtime reading from disk, AFAIK.
I originally tried this using addServerTemplate, but realized that updateTemplates won't refresh virtual Nitro templates, so was struggling to make things work in dev. Is there another way to do that?
There was a problem hiding this comment.
Hey @danielroe! Can I bump you on this one? 😅
314ef79 to
f832e7a
Compare
f832e7a to
1cf6123
Compare
1cf6123 to
85c34a2
Compare
c22f1d8 to
b794995
Compare
| addServerTemplate({ | ||
| filename: '#sanity-groq-queries-info', | ||
| getContents: () => | ||
| `export const queriesFilePath = ${JSON.stringify(queriesFilePath)}`, |
There was a problem hiding this comment.
couldn't we just do something like export const queries = ${JSON.stringify(queryArr}?
that way no need to write to file system.
There was a problem hiding this comment.
My understanding is that the getContents method of addServerTemplate is called once during build, even in dev mode. So something like this will always return 0:
let counter = 0
addServerTemplate({
filename: '#test',
getContents: () => `export const value = ${counter++}`,
})So as far as I can tell this approach won't work in dev mode because it won't return an up-to-date list of queries as they are added/modified. I tried this approach originally using updateTemplates but couldn't get that to refresh virtual Nitro templates.
This filesystem approach is only used for dev, in production the Rollup plugin exposes the #sanity-groq-queries virtual module that reads and inlines the file contents at build time, so no runtime writing/reading.
There was a problem hiding this comment.
it should call getContents again. it doesn't track dependencies, but when nitro rebuilds the server, the latest values should be present.
There was a problem hiding this comment.
Ah, I think I understand now. getContents does indeed get called on Nitro rebuild as you say, but the GROQ queries we need to whitelist are scanned from .vue files using the Vite plugin. AFAICT modifying those .vue files only triggers Vite rebuilds, not Nitro rebuilds.
From testing, updateTemplates seems to call builder:generateApp here, and generateApp looks at app.templates here, which again as far as I can tell doesn't do anything Nitro related (i.e. no reference to nuxt.options.nitro.virtual), but maybe I've missed something?
If there was an updateServerTemplates utility that would do it, but without a mechanism for triggering a Nitro rebuild, the only way I've found to share the GROQ queries discovered by Vite with Nitro in dev is with the filesystem approach.
There was a problem hiding this comment.
@danielroe I spent a bit more time re-validating my assumptions and tried three other approaches:
updateTemplates: Doesn't work because it only regenerates client templates, not server templates (see above!).rollup:reloadhook withaddServerTemplate: Triggers Nitro rebuild and successfully callsgetContents. Works technically, but has a race condition where:- Vite detects query change → updates in-memory array → triggers
rollup:reload. - Page HMR reload happens immediately.
- Nitro rebuild takes ~400ms to complete.
- During that window, query validation fails because the virtual module with whitelisted queries is not yet updated.
- Data fetching fails, no content rendered. Reloading/refocusing the page does re-execute the query, but the temporary error/empty page is not very pleasant DX.
- Vite detects query change → updates in-memory array → triggers
globalThis: Attempted to share queries on theglobalThisobject, but Nitro runs in an isolated worker thread so has a separateglobalThiscontext. I couldn't find any APIs to do main thread/worker communication.
The only way I can see to share queries without that race condition is the filesystem approach.
Note: I've also removed the existing globalThis fallback since the above validated that it doesn't actually work.
af90f04 to
41cf1bd
Compare
src/runtime/server/utils/proxy.ts
Outdated
| if (fromFileSystem) { | ||
| try { | ||
| const raw = await readFile(queriesFilePath, 'utf8') | ||
| return JSON.parse(raw) | ||
| } | ||
| catch (err) { | ||
| console.debug('Failed to read GROQ queries file, falling back to globalThis:', err) | ||
| } | ||
|
|
||
| const g = globalThis | ||
| return hasGroqQueries(g) && Array.isArray(g.__nuxt_sanity_groqQueries) ? g.__nuxt_sanity_groqQueries : [] | ||
| } |
There was a problem hiding this comment.
it doesn't look like this will be tree-shaken from the build
There was a problem hiding this comment.
Good point! I've refactored this in 6133537.
5f9eeec to
79beffd
Compare
79beffd to
7e5acfb
Compare
danielroe
left a comment
There was a problem hiding this comment.
thank you for bearing with me 🚀
🔗 Linked issue
N/A
❓ Type of change
📚 Description
Introduces support for proxying requests made using
useSanityQuery, this will allow apps that need to query private datasets to do so without having to use server components, and they can largely follow the same patterns as those used by apps that query public datasets.It will be up to the user to implement their own server handler for proxying requests. However this PR also ships a useful helper (
validateSanityQuery, see example in playground app) which makes this quite simple and means one handler can be used for all (most?) queries, rather than needing to create server handlers for each unique GROQ query users want to execute. The module now statically analyses all GROQ queries in the app at build time, so the helper can validate incoming queries against a whitelist to prevent use of this endpoint for other queries.