DEV Community

Cover image for Contributing to Vite+ Inside Stories — Vue Fes Japan 2026 Speaker Notes
naoki haba
naoki haba

Posted on

Contributing to Vite+ Inside Stories — Vue Fes Japan 2026 Speaker Notes

This is the script of my talk "Contributing to Vite+ Inside Stories" at Vue Fes Japan 2026. I gave the talk in Japanese. This is an English translation, with links to the Issues and PRs that I talk about.

Contributing to Vite+ Inside Stories | ドクセル

ドクセルはスライドやPDFをかんたんに共有できるサイトです

favicon docswell.com

Vue Fes Japan 2026 - Naoki Haba

Contributing to Vite+, and the View from Becoming a Team Member

favicon vuefes.jp

Introduction

Hello, everyone!
My slides are in English, but I will speak in Japanese today, because I am still learning English.
I hope you enjoy my talk!

Today, I want to look back on the contributions that I made to Vite+.

I will show the Issues and PRs that I worked on, and tell you what I was thinking at that time.

This is not a difficult technical talk. It is a look back at what I did, so please relax and listen.

At first, I worked on Vite+ as an individual contributor. After I joined the Core Team, my work changed. I started to investigate Issues and review PRs.

Today, I will also talk about how my way of thinking about open source software (OSS) changed during that time.

Let me introduce myself. My name is Naoki Haba.

In my free time, I work on Vite+ every day as a Core Team member.

What is Vite+?

Before the main topic, let me introduce Vite+.

Vite+ is a unified toolchain. It puts many tools for web development into one package.

In daily development, you start a development server, run tests, and lint and format your code.

To do this, you usually install many tools, and each tool has its own configuration and commands.

Vite+ puts these tools together, so you can use all of them from one CLI, vp.

You probably know many of the tools inside it.

It uses Vite for the development server and application builds, Vitest for tests, Oxlint for linting, and Oxfmt for formatting.

You run them with commands such as vp dev, vp test, and vp lint.

vp pack builds libraries, and it uses tsdown. I will talk about tsdown again later, in the migration part.

Now, let me tell you about my first contribution.

My journey as a contributor

My first contribution: vp create

The first problem that I worked on was in vp create.

vp create makes a new project from a template.

When I used it, I typed the name of a template that did not exist. The command showed the pnpm 404 error as it was.

With that error, it was hard to see what I did wrong.

I looked on GitHub, and someone had already reported the same problem as an Issue.

Incorrect registry causing project creation to fail #828

Describe the bug

When I want to create a new Vite Plus using the vp create <name>, the CLI fetches from https://registry.npmjs.org/<name> which returns error 404.

 ERR_PNPM_FETCH_404  GET https://registry.npmjs.org/create-vite-plus-app: Not Found - 404

Image

Reproduction

none

Steps to reproduce

Run vp create <name>

System Info

Environment:
  Version  24.14.0
  Source   lts

Tool Paths:
  node  /Users/azizbecha/.vite-plus/js_runtime/node/24.14.0/bin/node
  npm   /Users/azizbecha/.vite-plus/js_runtime/node/24.14.0/bin/npm
  npx   /Users/azizbecha/.vite-plus/js_runtime/node/24.14.0/bin/npx

vp v0.1.11

Local vite-plus:
  vite-plus  Not found

Tools:
  vite             Not found
  rolldown         Not found
  vitest           Not found
  oxfmt            Not found
  oxlint           Not found
  oxlint-tsgolint  Not found
  tsdown           Not found
Enter fullscreen mode Exit fullscreen mode

Used Package Manager

pnpm

Logs


Enter fullscreen mode Exit fullscreen mode

Validations

So I read the code and followed the steps that it ran.

I found that the command ran the package manager without first checking if the package existed on npm.

Because of this, when it did not find the template, it showed the pnpm error.

This is the PR that fixed it.

fix: add troubleshooting tips for npm template not found error #1055

Linked issue

resolves #828

Description

The behavior when specifying an invalid template name (e.g., vite-plus-app) with the vp create command has been improved.

With this update, the CLI now checks the npm registry before executing the package manager. If the template is not found (HTTP 404 error), it will display an error message and terminate the process immediately.

Before it runs the package manager, the command now asks the npm registry if the package exists.

If the registry returns 404, the command shows that it did not find the template, and then it stops.

The message also tells you that vp create --list shows the available templates.

This was my first PR to Vite+.

It was a small change. But I read the real code and fixed a problem that I had when I used the tool.

After that, I started to look at other Issues, a little at a time.

Migrating Node.js version settings

At about the same time, I wanted to use Vite+ at work.

But existing projects already have a Node.js version setting and configuration for many tools.

I wanted to know how to move those settings to Vite+.

So I looked at vp migrate.

This command migrates an existing project to Vite+.

Rewriting configuration files sounds easy. But if the migration changes the Node.js version that the project uses, that is a problem.

So I had to think about two things: how to read the old settings, and how to make the project work the same way after the migration.

My first task here was to migrate the Node.js version setting.

`vp migrate` handles migration from `.nvmrc` #1158

Description

As a dev using Vite+, I want my old nvm files being migrated over to the Vite+ way.

Suggested solution

Ask them if they want to migrate them over when being used.

Alternative

Also adhere to .nvmrc (meh)

Additional context

Same for other node version managers!

Validations

  • [x] Read the Contributing Guidelines.
  • [x] Confirm this request is for Vite+ itself and not for Vite, Vitest, tsdown, Rolldown, or Oxc.
  • [x] Check that there isn't already an issue requesting the same feature.

Two files can set the Node.js version for a project: .nvmrc and .node-version.

Vite+ uses .node-version, but projects that use nvm often have a .nvmrc file.

So I changed vp migrate. When it finds .nvmrc, it migrates the file to .node-version.

feat(migrate): migrate .nvmrc to .node-version during `vp migrate` #1159

🔗 Related Issues

Resolves: #1158

📚 Description

Vite+ previously retrieved the Node.js version from .node-version, but projects using nvm typically use .nvmrc.

With this change, automatic detection of .nvmrc and a migration feature to .node-version have been added as part of the vp migrate command execution process.

During migration, if a v prefix is present at the beginning, it is removed, while aliases such as lts/* are carried over as is.

Unit tests have confirmed that it works correctly regardless of the presence of the v prefix.

On the other hand, if unsupported aliases like node or stable are included, the system is designed to display a warning and skip the migration rather than failing.

Manual Test: .nvmrc Migration

$ mkdir /tmp/vp-migrate-test
$ echo '{ "name": "test-migrate", "version": "1.0.0" }' > package.json
$ echo 'v20.5.0' > .nvmrc
Enter fullscreen mode Exit fullscreen mode

Command

$ cd /tmp/vp-migrate-test && vp migrate --no-interactive
Enter fullscreen mode Exit fullscreen mode

Output (Excerpt)

.nvmrc detected. Auto-migrating to .node-version...
...
• Node version manager file migrated to .node-version

Final State

$ cat /tmp/vp-migrate-test/.node-version
20.5.0
  • .nvmrc (v20.5.0) -> Deleted
  • .node-version -> 20.5.0 (With the v prefix removed)

For example, if the file contains a version number such as 20.11.0, the command copies that value.

If the value starts with v, the command removes the v.

Up to here, the command only cleans up the value and copies it. But .nvmrc can contain values that are not version numbers.

I was not sure how to handle those values.

For example, .nvmrc can contain node instead of a version number.

node means "use the latest release of Node.js".

lts/* means "use the latest LTS release".

These two look similar, but they do not always give the same version.

So, at first, when the file contained node, the command showed a warning and stopped the migration.

If the command changed node to LTS without asking, the project could get an older Node.js version. I did not think that was correct.

But when I opened the PR, a reviewer suggested that converting it to LTS might be fine.

At about that time, I had also read an announcement. From Node.js 27, every major version moves to LTS after its release.

Because of this, I changed the command to convert node to lts/* and continue the migration.

But the new value does not mean the same thing as the old one, so the command writes a log message about the conversion.

At first, I thought this task was only about copying the contents of a configuration file.

But when I did it, I found cases where the command could not copy the old setting as it was.

I gave priority to keeping the meaning of the old setting. The reviewer gave priority to letting the migration continue.

Neither view was wrong. We had to decide which behavior was best for Vite+.

Talking about the direction with other members, before and after writing code, is one thing that makes OSS contribution interesting for me.

Up to here, I worked with Node.js, which I knew well. Next, I worked in a different area.

Adding Nushell support

That was support for Nushell.

Nushell is a shell, like Bash or Zsh. You run commands in a terminal with it.

Vite+ has a feature, vp env, that switches the Node.js version. The Issue asked to make this feature work in Nushell.

nushell env wrapper #1144

Description

As a developer using Vite+ I want to be able to source vp into nushell so that I can still use my main shell.

This includes wrapping the env use command and completions for vp.

Suggested solution

I've already implemented a env.nu that can be sourced in nushell like so 'source ~/.vite-plus/env.nu'

# Custom completer for the vp wrapper using fish completions
def "nu-complete vp" [context: string] {
    let fish_comp_path = ($env.HOME | path join ".vite-plus/completion/vp.fish")
    let fish_cmd = $"source '($fish_comp_path)'; complete '--do-complete=($context)'"
    fish --command $fish_cmd | from tsv --flexible --noheaders --no-infer | rename value description | update value {|row|
        let value = $row.value
        let need_quote = ['\' ',' '[' ']' '(' ')' ' ' '\t' "'" '"' "`"] | any {$in in $value}
        if ($need_quote and ($value | path exists)) {
            let expanded_path = if ($value starts-with ~) {$value | path expand --no-symlink} else {$value}
            $'"($expanded_path | str replace --all "\"" "\\\"")"'
        } else {$value}
    }
}
export def --env --wrapped vp [...args: string@"nu-complete vp"] { match $args {
    ["env", "use", ..] => {
        let out = (with-env { VITE_PLUS_ENV_USE_EVAL_ENABLE: "1" } {
                ^vp ...$args
            })
        let out_lines = ($out | lines)
        let exports = (
            $out_lines | find -r '^export ' | parse -r '^export (?P<key>\w+)=["'']?(?P<value>[^"'']+)["'']?' | reduce -f {} {|it, acc| $acc | insert $it.key $it.value}
        )
        if not ($exports | is-empty) {
            load-env $exports
        }
        let unsets = (
            $out_lines | find -r '^unset ' | parse -r '^unset (?P<key>\w+)' | get key
        )
        for key in $unsets { hide-env $key }
    }
    _ => { ^vp ...$args }
} }
Enter fullscreen mode Exit fullscreen mode

Alternative

No response

Additional context

No response

Validations

  • [x] Read the Contributing Guidelines.
  • [x] Confirm this request is for Vite+ itself and not for Vite, Vitest, tsdown, Rolldown, or Oxc.
  • [x] Check that there isn't already an issue requesting the same feature.

This part of Vite+ uses Rust. I had never used Rust in a real product before.

So I started by reading the existing code for Bash and Fish, to learn what it did.

I looked up the types and syntax that I did not know, and checked step by step how to do the same thing in Nushell.

But reading the Rust code was not enough. I also had to learn about the Nushell script that the code generates.

For example, look at this code:

# Does not work
source "$HOME/.vite-plus/env.nu"

# Works
source ~/.vite-plus/env.nu
Enter fullscreen mode Exit fullscreen mode

In the first example, the path that goes to source starts with $HOME.

In Bash and similar shells, you often write the home directory like this.

But in Nushell, $HOME inside double quotes does not expand. Also, Nushell writes environment variables as $env.HOME.

So I thought that I only had to change that. But it still did not work.

The reason is that Nushell's source decides which file to load when it parses the script, before any command runs.

So you cannot use a runtime environment variable in that path.

I read the Nushell documentation again, and changed the path to start with a tilde, as in the second example.

With a tilde, Nushell can find the home directory when it parses the script.

feat(env): add Nushell support via env.nu wrapper #1312

resolves #1144

While comprehensive support for Nushell seems to be under development in #1305, that pull request has a large impact. Therefore, I have limited the scope of this change to env support to align with the existing issue.

# nushell

# 1. Setup
vp env setup
source ~/.vite-plus/env.nu  # or restart Nushell

# 2. Set a specific version
vp env use 20
# Using Node.js v20.20.2 (resolved from 20)
$env.VP_NODE_VERSION
# 20.20.2

# 3. Resolve from .node-version
vp env use
# Using Node.js v22.18.0 (resolved from .node-version)
$env.VP_NODE_VERSION
# 22.18.0

# 4. Completion (requires Fish)
vp <Tab>
# add        Add packages to dependencies
# build      Build application
# cache      Manage the task cache
# check      Run format, lint, and type checks
# ... (all subcommands shown)

# 5. False detection regression (bash launched from Nushell)
# NU_VERSION is inherited by child processes, but VP_SHELL_NU is not set,
# so vp correctly uses POSIX eval mode inside bash.
bash -c 'vp env use 20 && echo $VP_NODE_VERSION'
Enter fullscreen mode Exit fullscreen mode

The Rust code only makes a string. But I had to think about how Nushell reads that string.

So I ran the script in Nushell to check each change.

Before this, I had never written Rust code for a real product.

I work as a frontend engineer, so I do not use Rust at work.

I had solved some LeetCode problems in Rust, and that was all.

When you study alone, you usually stay with what you already know. In OSS, you cannot do that.

If you want to fix a problem, you must learn the language or the specification, even if you do not know it.

In this case, I had to learn about Nushell as well as Rust.

I did not understand everything at the start. I looked things up, tried them, got help in reviews, and learned a little at a time.

It was good study for me, and it also helped a tool that I use. I think that is a very good deal.

Joining the Core Team

I continued like this. I investigated Issues that interested me, and I opened PRs.

About one month after my first PR, the Vite+ Core Team invited me to join.

I was very surprised.

It was my first time on the core team of an OSS project. The members were engineers that I had looked up to for a long time.

I never thought that I would work on the same team as them.

After I joined the team, I started to look at Issues and PRs from other people, in addition to my own PRs.

For example, when someone reports an Issue, I first try to reproduce it on my machine.

If I can reproduce it, I look for the cause. If the report does not have enough information, I ask the reporter.

I also review more PRs from other people.

But the code alone does not always tell you why a change is necessary.

So I read the related Issues and past discussions. When necessary, I also read the code of projects other than Vite+.

Now I will show some of the work that I did as part of this.

Migrating from tsdown to Vite+

The first one is a problem in the migration from tsdown to Vite+.

tsdown is a tool that builds TypeScript libraries.

Vite+ could already migrate projects from tsdown. But after the migration, some projects got TypeScript errors.

no `tsdown/client` equivalent #1500

Describe the bug

Similar to vite/client, tsdown includes a tsdown/client file to register its bundler features with Typescript.

It would be useful if this was re-exported by Vite+, perhaps in vite-plus/pack/client (I see that vite/client is re-exported as vite-plus/client).

Reproduction

https://github.com/open-circle/schema-benchmarks/pull/124

Steps to reproduce

replace tsdown/client in schemas/tsconfig.json with vite-plus/pack/client

System Info

admin@MacBookPro schema-benchmarks % vp env current
VITE+ - The Unified Toolchain for the Web

Environment:
  Version       24.15.0
  Source        .node-version
  Source Path   /Users/admin/schema-benchmarks/.node-version
  Project Root  /Users/admin/schema-benchmarks

Tool Paths:
  node  /Users/admin/.vite-plus/js_runtime/node/24.15.0/bin/node
  npm   /Users/admin/.vite-plus/js_runtime/node/24.15.0/bin/npm
  npx   /Users/admin/.vite-plus/js_runtime/node/24.15.0/bin/npx
admin@MacBookPro schema-benchmarks % vp --version
VITE+ - The Unified Toolchain for the Web

vp v0.1.20

Local vite-plus:
  vite-plus  v0.1.20

Tools:
  vite             v8.0.10
  rolldown         v1.0.0-rc.17
  vitest           v4.1.5
  oxfmt            v0.46.0
  oxlint           v1.61.0
  oxlint-tsgolint  v0.22.0
  tsdown           v0.21.10

Environment:
  Package manager  pnpm v10.33.2
  Node.js          v24.15.0 (.node-version)
Enter fullscreen mode Exit fullscreen mode

Used Package Manager

pnpm

Logs


Enter fullscreen mode Exit fullscreen mode

Validations

I investigated and found that references to the tsdown/client type definitions stayed in the project after the migration.

So I added a matching export, vite-plus/pack/client, to Vite+, and made vp migrate rewrite the references.

But import statements were not the only place with these references. They were also in type declarations, in tsconfig.json, and in other places.

migrate: rewrite `tsdown/client` and `vite/client` in TS imports and tsconfig.json `types` array #1502

Description

When running vp migrate, tsdown is removed as a dependency and replaced with vite-plus equivalents. However, two patterns are currently left unrewritten, causing broken imports and TypeScript errors after migration.

relates: https://github.com/voidzero-dev/vite-plus/pull/1501

Cases to handle

1. import "tsdown/client" in .ts files

// before
import "tsdown/client";

// after
import "vite-plus/pack/client";
Enter fullscreen mode Exit fullscreen mode

Real-world example: https://github.com/Tarrowren/css-class-intellisense/c91dd77dc1fb5acf/lezer/less/test/test-less.ts#L1

2. types array in tsconfig.json

// before
{ "compilerOptions": { "types": ["tsdown/client"] } }
{ "compilerOptions": { "types": ["vite/client"] } }

// after
{ "compilerOptions": { "types": ["vite-plus/pack/client"] } }
{ "compilerOptions": { "types": ["vite-plus/client"] } }
Enter fullscreen mode Exit fullscreen mode

Real-world example: https://github.com/ascorbic/cirrus/blob/98aec98dc56de58f8d6010565cef6cfc5c62311e/packages/pds/tsconfig.json#L5

Suggested solution

Case 1 — import in .ts files

Add a new ast-grep rule to REWRITE_TSDOWN_RULES (import_rewriter.rs) to match import "tsdown/client" and rewrite it to import "vite-plus/pack/client", alongside the existing tsdown rule.

Case 2 — tsconfig.json types array

Add a new function to packages/cli/src/utils/tsconfig.ts using the existing jsonc-parser dependency to rewrite entries in compilerOptions.types:

  • "tsdown/client" → "vite-plus/pack/client"
  • "vite/client" → "vite-plus/client"

Call this function from migrator.ts alongside cleanupDeprecatedTsconfigOptions().

Alternative

  • Document as manual steps instead of automating. (Even though I don't want to)

Additional context

No response

Validations

  • [x] Read the Contributing Guidelines.
  • [x] Confirm this request is for Vite+ itself and not for Vite, Vitest, tsdown, Rolldown, or Oxc.
  • [x] Check that there isn't already an issue requesting the same feature.

So I had to add a migration step for each of these formats.

#1541 rewrites import and declare module statements.

feat(migration): rewrite tsdown/client import and declare module statements #1541

resolves #1502

This PR depends on #1501. When you're ready, please make sure #1501, #1541, and #1542 are all merged together.

I originally thought about including this in #1501, but I decided to pull it into a separate PR so the scope wouldn't get too wide. I figured it would be easier to review if we kept the Rust and TypeScript changes apart rather than mixing them into one.

Let me know if you’d prefer a different merge strategy and I can easily adjust things.

#1542 rewrites compilerOptions.types in tsconfig.json.

feat(migration): rewrite tsdown/client and vite/client in tsconfig compilerOptions.types #1542

resolves #1502

This PR depends on #1501. When you're ready to merge, please make sure #1501, #1541, and #1542 are all merged at the same time.

I thought about putting this together with #1501, but I decided to keep it separate so the scope didn't get too messy. I wanted to keep the Rust and TypeScript parts in different PRs to make the review a bit easier for everyone.

If you’d prefer to have these combined for the merge, just let me know and I can adjust it.

Replacing a dependency sounds easy. But I had to make sure that TypeScript still worked after the migration.

Supporting GitLab CI with setup-vp

Next, let me talk about CI.

If you use Vite+ at work, you want to use it in CI too, and not only on your machine.

For this, I worked on setup-vp.

setup-vp installs Vite+ in a CI environment, and sets up dependencies and caches.

At that time, it supported GitHub Actions, but not GitLab CI.

So, to use Vite+ on GitLab, you had to write the install steps yourself.

Add GitLab CI/CD template support #88

We've received several requests for a GitLab CI/CD version of our existing Vite+ GitHub Action. Currently, we only provide voidzero-dev/setup-vp for GitHub, leaving GitLab users without an official, reusable setup method. You can find more details in this issue: https://github.com/voidzero-dev/vite-plus/issues/1920

Objective

Our goal is to add official GitLab CI/CD support, enabling users to easily integrate a Vite+ setup template directly into their .gitlab-ci.yml.

For example, users should be able to use the following:

include:
  - remote: 'https://raw.githubusercontent.com/voidzero-dev/setup-vp/v1/gitlab/setup-vp.yml'
Enter fullscreen mode Exit fullscreen mode

The template should replicate the functionality of the GitHub Action, including Node.js and package manager setup, Vite+ installation, and dependency caching.

Proposed Changes

I propose adding a dedicated folder for the GitLab CI template:

gitlab/
└── setup-vp.yml
Enter fullscreen mode Exit fullscreen mode

Since GitLab supports remote YAML includes, hosting the template in this repository will work seamlessly as long as it remains public.

For more information on how this works, please refer to the GitLab documentation:

I will take care of this implementation.

I added a template, so that you do not have to write those steps yourself on GitLab CI.

feat: add GitLab CI/CD integration for setup-vp #97

resolves #88

I thought it would be best to document the GitLab integration features as an RFC, so I’ve put everything together along with a diagram of the overall flow.

rfcs/assets/gitlab-runtime-flow.svg

gitlab-runtime-flow

Now I am a collaborator on setup-vp, and I help to maintain it.

For this work, I did not start by writing code. First, I wrote the design in an RFC.

An RFC is a document that proposes a feature or a design, so that the team can discuss it.

In the RFC, I used a diagram and text to show how to use setup-vp in GitLab CI and the order of the steps.

The GitHub Actions code already existed, so at first it looked like we could reuse it.

But GitLab CI reads inputs and sets PATH in a different way, so that code does not work there.

So I kept the usage as similar as possible, and wrote steps only for GitLab.

Development continued after that. Now several CI services share the same install and authentication steps.

setup-vp also supports caching, and it uses the cache feature of both GitHub Actions and GitLab.

I did not build all of this myself.

After I added GitLab support, other members added more features.

A feature can grow from one PR as other people add to it. I think that is one of the interesting things about OSS.

Up to here, I showed things that I built. After I joined the team, I also started to investigate the causes of Issues.

Investigating a SolidStart preview failure

One example is an Issue where vp preview failed on SolidStart v2.

[Bug]: vp preview fails on SolidStart v2 — looks for dist/server but build is in .output/server #2416

Describe the bug

vp preview fails on a SolidStart v2 project. vp build succeeds, but vp preview throws:

error when starting preview server:
Error: Could not find the SolidStart server entry in <project>/dist/server
    at resolvePreviewServerEntry (file:///.../@solidjs/start/dist/config/dev-server.js:108:15)
    at configurePreviewServer (.../dev-server.js:20:54)

The build output lands in .output/server (Nitro's standard layout — the build log shows nitro ... preset: node-server writing .output/server/index.mjs), but SolidStart 2's configurePreviewServer hook hardcodes dist/server as the server-entry location. So preview looks in the wrong directory.

This is distinct from #1291 (SolidStart v1 / Vinxi dev-server support, closed). SolidStart v2 uses native Vite (no Vinxi), so the preview hook resolution is the remaining mismatch.

Steps to reproduce

  1. Scaffold a SolidStart v2 project with the Tailwind template:
    pnpm create solid@latest
    # select: SolidStart, TypeScript, with-tailwindcss
    Enter fullscreen mode Exit fullscreen mode
  2. Add the Vite+ toolchain:
    vp migrate
    Enter fullscreen mode Exit fullscreen mode
  3. Build:
    vp build
    # succeeds — output written to .output/server and .output/public
    Enter fullscreen mode Exit fullscreen mode
  4. Preview:
    vp preview
    # fails: "Could not find the SolidStart server entry in .../dist/server"
    Enter fullscreen mode Exit fullscreen mode

Expected behavior

vp preview should serve the production build — either by finding the server entry at .output/server (where Vite+/Nitro actually wrote it), or by delegating preview to Nitro's server for frameworks that use Nitro.

Actual behavior

vp preview errors out looking for dist/server, which never exists in a Vite+/Nitro build.

Environment

  • Vite+ / vp via vp migrate (vite-plus core 0.2.8)
  • SolidStart 2 (@solidjs/start@^2.0.0, vite@^8, nitro@^3 beta)
  • OS: Ubuntu 24.04 / Pop!_OS
  • Node 24

Workaround

node .output/server/index.mjs (the start script) runs the production server correctly. The only broken piece is vp preview.

Related

  • #1291 (closed) — SolidStart v1 / Vinxi vp dev support. Different: this is SolidStart v2 + vp preview, where the failure is the preview server-entry path mismatch.

The error occurred when the user used Vite+, so at first it looked like a Vite+ problem.

But that was not enough to find the cause.

So I made two projects to test: one with Vite+, and one with Vite only.

The same error occurred with Vite only.

So I knew that the problem occurred without Vite+.

Then, was it a Vite problem? I still did not know.

The cause could be in SolidStart, so I read the stack trace to find where it failed.

I investigated more, and found that the cause was in the connection between SolidStart and Nitro.

Nitro is a tool that builds server applications.

Nitro writes the built server files to .output/server, but SolidStart looked for them in a different directory.

Because of this, SolidStart failed before Nitro's preview step started.

So I opened an Issue in the SolidStart repository, and shared the reproduction repositories and what I found.

[Bug?]: Vite preview fails with Nitro v3 because SolidStart looks for dist/server #2291

Duplicates

  • [x] I have searched the existing issues

Latest version

  • [x] I have tested the latest version

Current behavior 😯

I’d appreciate the maintainers’ guidance on the preferred approach here. If the analysis and proposed direction below look correct, I’d be happy to contribute a fix.

I am a member of the Vite+ team. I found this issue while investigating a bug reported here: https://github.com/voidzero-dev/vite-plus/issues/2416

The same behavior is reproducible with both Vite+ and official Vite:

The official Vite reproduction uses vite@8.2.0 and does not have either vite-plus or @voidzero-dev/vite-plus-core installed.

vite build succeeds and generates the Nitro server entry at:

.output/server/index.mjs

Nitro records this entry in .output/nitro.json:

{
  "serverEntry": "server/index.mjs",
  "commands": {
    "preview": "node ./server/index.mjs"
  }
}
Enter fullscreen mode Exit fullscreen mode

However, vite preview fails with the following error:

error when starting preview server:
Error: Could not find the SolidStart server entry in <project>/dist/server

The stack trace points to SolidStart's resolvePreviewServerEntry:

at resolvePreviewServerEntry (.../@solidjs/start/dist/config/dev-server.js:108:15)
at configurePreviewServer (.../@solidjs/start/dist/config/dev-server.js:20:54)
at preview (.../vite/dist/node/chunks/node.js:...)

SolidStart's resolvePreviewServerEntry only searches for:

dist/server/entry-server.js
dist/server/entry-server.mjs

Relevant implementation: https://github.com/solidjs/solid-start/blob/main/packages/start/src/config/dev-server.ts

Both SolidStart and Nitro register a configurePreviewServer hook. The resolved hook order is:

solid-start-dev-server
nitro:preview

SolidStart's hook runs first and throws an error because dist/server/entry-server.* does not exist. This stops Vite's hook loop before reaching Nitro's preview hook.

I have shared the detailed investigation here: https://github.com/voidzero-dev/vite-plus/issues/2416#issuecomment-5296733248

Expected behavior 🤔

When Nitro's Vite plugin controls the production server, SolidStart's preview hook should not require its standalone dist/server/entry-server.* output.

Instead, vite preview should delegate preview handling to Nitro and serve the generated .output/server/index.mjs build.

Steps to reproduce 🕹

Reproduction using official Vite:

https://github.com/naokihaba/repro-vite-plus-2416-plain-vite

Steps:

  1. Clone the reproduction:

    git clone https://github.com/naokihaba/repro-vite-plus-2416-plain-vite.git
    cd repro-vite-plus-2416-plain-vite
    Enter fullscreen mode Exit fullscreen mode
  2. Install dependencies:

    pnpm install
    Enter fullscreen mode Exit fullscreen mode
  3. Build the application:

    pnpm build
    Enter fullscreen mode Exit fullscreen mode

    The build succeeds and generates .output/server/index.mjs.

  4. Start the preview server:

    pnpm preview
    Enter fullscreen mode Exit fullscreen mode

    Preview fails while looking for dist/server/entry-server.*.

The generated Nitro server works when started directly:

pnpm start
Enter fullscreen mode Exit fullscreen mode

Context 🔦

This was initially reported as a Vite+ bug. I investigated it as a member of the Vite+ team and created a control reproduction using official vite@8.2.0.

Because the same failure occurs without Vite+, this appears to be an integration issue between SolidStart's preview hook and Nitro's preview handling.

SolidStart's configureServer hook already avoids taking control when another plugin manages the development server:

if ("dispatchFetch" in serverEnv) return;

// another plugin is controlling the dev server
if (!isRunnableDevEnvironment(serverEnv)) {
  return;
}
Enter fullscreen mode Exit fullscreen mode

I think configurePreviewServer needs equivalent delegation or skip behavior when Nitro controls preview.

The exact configureServer condition may not be directly reusable because the preview hook does not necessarily expose the same runtime environment. The important behavior is that SolidStart should avoid resolving dist/server/entry-server.* when another plugin, such as Nitro, owns the preview server.

Your environment 🌎

System:
  OS: macOS 26.5.2
  CPU: arm64

Binaries:
  Node: 24.19.0
  pnpm: 11.21.0

npmPackages:
  @solidjs/start: 2.0.0
  nitro: 3.0.260610-beta
  solid-js: 1.9.14
  vite: 8.2.0
Enter fullscreen mode Exit fullscreen mode

After that, the SolidStart team fixed the problem in PR #2294, and released the fix in version 2.0.1.

fix(dev-server): delegate preview to Nitro when building with Nitro v3 #2294

Problem

When using the official Nitro v3 Vite plugin with preset: "node-server", vite preview fails with:

Error: Could not find the SolidStart server entry in <project>/dist/server

SolidStart's configurePreviewServer hook runs before the nitro:preview hook. Since Nitro stores the server entry at .output/server/index.mjs instead of dist/server/entry-server.*, resolvePreviewServerEntry throws, stopping Vite's hook loop before Nitro's hook can handle the request.

Fix

Check for the Nitro build output marker (.output/nitro.json). If present and the file declares a serverEntry, SolidStart skips its own preview middleware and lets the nitro:preview hook handle requests.

Verification

  • vite build + vite preview with Nitro v3 → succeeds (preview delegated to Nitro)
  • vite build + vite preview without Nitro → unchanged behavior (SolidStart middleware still used)
  • No Nitro build → dist/server/entry-server.* checked as before

Fixes #2291

@solidjs/start 2.0.1 release

I updated my projects and confirmed that the problem was gone.

In this case, I did not change any code.

But when someone reports a problem in Vite+, the correct fix is not always in Vite+.

I think finding the cause and taking it to the correct project is also a contribution to OSS.

As I showed, after I joined the team, I started to work on the project in many ways, and writing code was only one of them.

At first, I mostly fixed problems that I had myself.

For example, I thought that my first PR for vp create was done when the command showed that it did not find the template.

But after I joined the team and started to look at other people's Issues and PRs, I thought more before writing code.

In the SolidStart case, if I had changed Vite+ immediately, I could have changed code that was not the cause.

So now I investigate the problem first, and think about where the fix should go.

Before, other people reviewed my PRs. Now, I also think about other people's proposals together with them. That is the biggest change from my early days.

AI and OSS contributions

Up to here, I talked about investigating Issues and reviewing PRs.

I also use AI when I read code or implement changes.

It helped me most when I read code in a language that I did not know well.

But using AI for my own PRs is different from receiving PRs from other people as a team member.

Let me tell you what I learned from both sides.

Using AI to understand code

First, back to the Nushell work.

For Nushell, I used AI to read the Rust code.

When I saw a type or syntax that I did not know, I asked the AI what it meant.

I also asked it to make an HTML animation of the steps that the code runs.

In a language that you do not know, the syntax alone can stop you.

So getting an explanation at that moment helped me a lot.

But when you read an AI's explanation, you can feel that you understand when you do not.

In the Nushell example, even if I understood the Rust code, I still did not know if the generated source command worked.

So I ran it in Nushell, and checked the original code and the specification.

When you open a PR, a reviewer may ask why you wrote the code that way.

"Because the AI said so" is not an explanation.

So I check what the AI tells me, and I make sure that I can explain it in my own words.

Reviewing contributions made with AI

As a reviewer, I see a different side.

Now, implementation PRs sometimes arrive less than five minutes after someone opens an Issue.

A fast fix is good.

But for some Issues, the team has not yet decided whether to make the change.

When a PR arrives at that stage, the maintainers must check the code, and also decide whether the change is necessary at all.

Sometimes I ask a question in the review, and the author does not reply.

Then nobody can confirm the intent of the change, and the PR stops.

AI can make writing code faster.

But you still need time to understand that code and check that it works.

If you leave that work to the maintainers, you only move the work to them.

Understanding the problem before writing code

So I think you can also use AI for research and understanding before you write code.

For example, you find an open Issue on GitHub.

But an open Issue is not always waiting for an implementation.

The team may still be discussing the direction, or may not have decided whether to fix it at all.

So first, ask an AI to summarize the discussion in the Issue.

Then read the original comments too, and find out what the team has decided and what it has not.

If it is still not clear, tell the maintainers what you found, and ask them.

In the SolidStart Issue, I also shared the conditions that reproduce the problem and what I found, and asked about the direction of the fix.

With AI, writing code is much easier than before.

But opening a PR is not the goal. You must understand the problem, test the change yourself, and take responsibility for it.

So, before you ask AI to write code, use it to help you understand the problem.

I think this way of using AI is important for OSS contributions.

Conclusion

Today, I showed some of the Issues and PRs that I worked on.

At first, I only fixed a problem that I had with vp create. As I continued, I worked on migration, shells, CI, and more.

I did not know these areas well at the start.

I looked up what I did not know, tried it, opened a PR, and learned from the review. I did this again and again.

Over time, I learned the Vite+ code. I also learned what the project discusses and which direction it takes.

Now, what I learned then helps me when I look at other people's Issues and PRs.

Today I talked mostly about code. But you can contribute to OSS without writing code.

The SolidStart case is an example.

I reproduced the problem, found the cause, and shared it in an Issue. That was all.

Another person changed the code.

Also, if you try to reproduce a reported Issue on your machine, that helps.

If your comment includes your versions and the steps to reproduce the problem, it is easier to find the cause.

After a fix, a comment such as "I updated and it works now" also helps.

Fixing documentation, or telling the team what you think after you use a tool, is also a real contribution.

After I started to investigate Issues and review PRs myself, I saw how much these reports help.

Maybe you are interested in OSS, but you are not sure that you can help.

If so, start with one thing that you noticed in a tool that you use every day.

For example, when you have a problem, look on GitHub for the same Issue.

If you find it, try to reproduce it on your machine. If you find the conditions that cause it, write them in a comment.

That alone can help the maintainers who investigate the problem.

If you want to implement something, first read the Issue discussion and the contribution guide.

If the team has not decided the direction yet, tell them what you found and ask them.

Now AI can help you, so it is easier than before to read and understand code that you do not know.

I did not plan to join the Core Team.

I looked into a problem that I had when I used the tool, and I sent a fix. Because of that, I still work on Vite+ today.

Finally, let me tell you why I continue to contribute to OSS.

I look into a problem, change the code, and that makes a tool that I use better.

That is interesting.

But there is more. I can discuss the same project and review code with engineers that I would not meet at work.

These connections with people are a big reason why I continue with OSS.

It started when I fixed a problem that I had with a tool.

Now, I build Vite+ together with the people that I met there.

Behind the code of OSS, there are people who think and build together.

I want to continue to build OSS together with these people.

Thank you for listening.

Top comments (0)