remix: Vite build fails, recursively creates public build directories when using assetsBuildDirectory

Reproduction

https://stackblitz.com/edit/remix-run-remix-fa3huu?file=package.json

System Info

System:
    OS: Linux 5.0 undefined
    CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 0 Bytes / 0 Bytes
    Shell: 1.0 - /bin/jsh
  Binaries:
    Node: 18.18.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 9.4.2 - /usr/local/bin/npm
    pnpm: 8.9.2 - /usr/local/bin/pnpm
  npmPackages:
    @remix-run/css-bundle: * => 2.2.0 
    @remix-run/dev: * => 2.2.0 
    @remix-run/eslint-config: * => 2.2.0 
    @remix-run/node: * => 2.2.0 
    @remix-run/react: * => 2.2.0 
    @remix-run/serve: * => 2.2.0 
    vite: ^4.5.0 => 4.5.0

Used Package Manager

npm

Expected Behavior

When running npm run build in an app setup with vite that has assetsBuildDirectory set to public/mybase/build, it is expected that the build would succeed and produce the public build artifacts at public/mybase/build.

Actual Behavior

If the public folder does not exist at all, then the build passes and creates public/mybase/build.

However, if the public directory exists, it tries to recursively create directories:

public
- mybase 
- - build
- - - mybase
- - - - build

Eventually, the build fails with the error:

error during build:
Error: ENAMETOOLONG: name too long, mkdir '/path/to/app/public/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase/build/mybase'

About this issue

  • Original URL
  • State: closed
  • Created 8 months ago
  • Comments: 17 (15 by maintainers)

Most upvoted comments

I’ve opened a PR to fix this, but to confirm whether this works for you in the short term, try setting build.copyPublicDir to false in your Vite config:

export default defineConfig({
  build: {
    copyPublicDir: false
  },
  // etc.
});

It seems like the way Remix has worked up to this point clashes with the way Vite typically works in terms of the public directory.

For now at least I think we’d want to maintain the same directory structure as the existing Remix compiler to make migration as painless as possible. To do this, we’ll probably need to disable Vite’s build.copyPublicDir option internally.

Anyone else getting this warning?

(!) The public directory feature may not work correctly.
outDir /Users/bob/web/foo/public/build and publicDir /Users/bob/web/foo/public are not separate folders.

That’s on the default settings.

Thanks for the reproduction! I was also seeing some tricky behavior when customizing assetsBuildDirectory in my remix app (not well organized for others to see, but just for reference https://github.com/hi-ogawa/ytsub-v3/pull/519/). What I ended up was to disable vite’s automatic ./public directory copying behavior by passing publicDir: false when vite build. I forked your repro to test this idea and it seems okay: https://stackblitz.com/edit/remix-run-remix-qnj73f?file=vite.config.ts

export default defineConfig((env) => ({
  publicDir: env.command === 'build' ? false : undefined,
  plugins: [
    remix({
      assetsBuildDirectory: 'public/mybase/build',
      publicPath: '/mybase/build',
    }),
    tsconfigPaths(),
  ],
}));

This workaround might require extra script to copy some assets afterwards, but for the setup of your repro, ./public is assumed to be the root of the app, so I think it’s fine. I confirmed favicon.ico is served for each case:

# after npm run dev
curl  http://localhost:5173/favicon.ico

# after npm run build && npm run start
curl  http://localhost:3000/favicon.ico

It would be nice to support the vite base option that can be provided to vite.config.ts and respect that for the build outputs. This is something that we’ve wanted at my company since Remix launched, because we host many apps on the same origin, just different base paths.

Regarding this, I think this might have fundamentally different technical challenge since this will not be only about vite’s base option to control where vite recognize/emits assets, but also remix/react-router has to be aware of “base” path for the entire routing/navigation logic. I’m not sure if react-router already has such feature now, or any plan to do this.


Also FYI, remix plugin currently sets remix config’s publicPath as vite’s base option internally.

https://github.com/remix-run/remix/blob/bfa2bbcece0ee4bfe7f501bbde935e5fcdfa20f1/packages/remix-dev/vite/plugin.ts#L513

So by default (which is vite base = remix publicPath = /build/), the generated assets look like this:

public/
- favicon.ico
- build/
  - favicon.ico  (<---- copied from vite `publicDir` to vite `base`)
  - manifest.json (vite manifest)
  - manifest-xxx.json (remix manifest)
  - assets/
    - ... all vite generated js chunks etc...

I was wondering if there is a way to achieve something similar without relying on base (for example, only by manipulating vite outDir?).

EDIT: I forgot about current remix’s publicPath is also intended to be used with external domain, example from https://remix.run/docs/en/main/file-conventions/remix-config#publicpath

 publicPath: "https://static.example.com/assets/"

So, the usage of base seems actually correct for this scenario.

I’ve opened a PR to fix this, but to confirm whether this works for you in the short term, try setting build.copyPublicDir to false in your Vite config:

export default defineConfig({
  build: {
    copyPublicDir: false
  },
  // etc.
});

I tried it briefly and it appears that it’s working as intended. I didn’t test too deeply or deploy anything yet, but at the very least the build passes and there are no recursive directories being created at build time.

It would be nice to support the vite base option that can be provided to vite.config.ts and respect that for the build outputs. This is something that we’ve wanted at my company since Remix launched, because we host many apps on the same origin, just different base paths.