Skip to content

RFC(v1): Virtual codegen modules for Next.js strict layout (env/ stays user-authored only) #1402

Description

@yamcodes

Summary

Exploratory vision for v1: keep the user-facing strict layout tree small and intentional, and move Next.js codegen artifacts out of env/ into tool-owned virtual modules.

Supersedes the file-based direction in #1304 as the preferred end state. Related precursor: #1307 (Nuxt virtual alias — ships on dev, additive).

Problem

Next.js strict layout still requires manual server wiring:

import arkenv from "@arkenv/nextjs/server";
import { env as clientEnv } from "./client";

export const env = arkenv(
  { DATABASE_URL: "string" },
  { extends: [clientEnv] },
);

#1304 proposed generating server.gen.ts beside env.gen.ts to fix that. Design review found deeper issues with putting more machine output under env/:

  • env.gen.ts + server.gen.ts naming is asymmetric and easy to misread
  • generated/ + .gen is redundant marking
  • Colocating *.gen.ts next to editable schemas invites misclicks
  • env/internal/ + env/generated/ together makes env/ feel like a mini-package, not an env validator
  • Flattening internal/shared.tsenv/shared.ts is a DX footgun (invites @/env/shared imports); app code should only import from client or server

Vision (v1)

User-facing tree (unchanged intent)

env/
  client.ts
  server.ts
  internal/
    shared.ts   # private composition only — not a public import surface

internal/ stays on purpose: awkward path as a guardrail so shared schema is not consumed directly.

Machine-owned factories (new)

Codegen writes outside the schema tree (e.g. .arkenv/ or equivalent tool cache) and is exposed via stable virtual imports registered by withArkEnv:

// env/client.ts
import arkenv from "arkenv/gen/client";

// env/server.ts
import arkenv from "arkenv/gen/server";

export const env = arkenv({
  DATABASE_URL: "string",
});

Desired properties:

Exact specifier spelling (arkenv/gen/* vs #arkenv/* vs @arkenv/nextjs/gen/*), on-disk cache location, and commit-vs-gitignore policy are open judgment calls for this RFC.

Relationship to #1307

Does not contradict — sets the stage.

#1307 (Nuxt, → dev) This RFC (Next.js, → v1)
Mechanism Virtual module alias Virtual module (+ tool-owned artifacts)
What the alias points at User’s env/client.ts Generated client/server factories
Import in server.ts Still @arkenv/nuxt/server Prefer arkenv/gen/server (or similar)
Compatibility Additive / non-breaking Likely breaking import-path migration
Files on disk in env/ None added None added (goal)

#1307 proves the host-plugin can own resolution and auto-merge without committing factories next to schemas. v1 extends that idea to Next.js, where we still need some generated factory (for runtimeEnv), but it should not live in the user-facing tree.

Ship #1307 on dev as-is. Do not block it on this RFC. When this lands in v1, consider converging naming (#arkenv/… vs arkenv/gen/…) across hosts as a separate consistency pass.

Relationship to #1304

#1304 remains the original problem statement and a valid interim file-based approach if we ever need a smaller step. This issue is the preferred v1 end state. Prefer implementing this over #1304’s server.gen.ts under env/generated/.

Open questions (human judgment)

  1. Specifier namespacearkenv/gen/client, #arkenv/gen/client, or @arkenv/nextjs/gen/client?
  2. On-disk location.arkenv/, node_modules/.arkenv/, or another cache? Committed for CI types, or always regenerated in withArkEnv / build?
  3. Flat layout — same virtual surface, or keep a simpler single-module story?
  4. Migration — soft compat for ./generated/env.gen imports in v1, or hard cut?
  5. Cross-host alignment — how closely should Next.js match (v1) Auto-extend client env in Nuxt strict layout via virtual module alias #1307’s #arkenv/client-env auto-extend model vs explicit generated server factory imports?

Non-goals (for this RFC)

Success sketch

  • Strict env/ contains only user-authored modules (client, server, internal/*)
  • Next.js strict server.ts needs no manual extends: [clientEnv]
  • Generated artifacts are not siblings of editable schema files
  • Docs/CLI scaffolds teach virtual imports as canonical

Metadata

Metadata

Assignees

No one assigned

    Labels

    @arkenv/nextjsIssues or Pull Requests involving the Next.js integration for ArkEnvenhancementNew feature or improvementready for humanNeeds a judgment call or design decision before implementationrfcA proposal open for discussion, inviting feedback before writing code. May or may not land as-is

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions