Wyatt Strother
Wyatt Strother

Reputation: 33

Why is Tailwind not installing correctly in my Vercel deployment? (Vite/Sveltekit)

I recently tried to deploy a SvelteKit project repository on Vercel and an API endpoint I wrote to pass in utility classes and invoke Tailwind programmatically doesn't work because of the way Vercel is building the app. It works fine in my local environment, both running the vite dev and vite build && vite preview commands.

Page to Investigate

https://streaming-app-tau.vercel.app/api/css?css=bg-red-500

Steps to Reproduce

The endpoint listed above invokes postcss / tailwind programmatically but produces a 500 error with the following stack trace:

Error: ENOENT: no such file or directory, open '/var/task/node_modules/tailwindcss/lib/css/preflight.css'
    at Object.openSync (node:fs:603:3)
    at Object.readFileSync (node:fs:471:35)
    at preflight (/var/task/node_modules/tailwindcss/lib/corePlugins.js:491:66)
    at registerPlugins (/var/task/node_modules/tailwindcss/lib/lib/setupContextUtils.js:794:61)
    at createContext (/var/task/node_modules/tailwindcss/lib/lib/setupContextUtils.js:1196:5)
    at getContext (/var/task/node_modules/tailwindcss/lib/lib/setupContextUtils.js:1253:19)
    at /var/task/node_modules/tailwindcss/lib/lib/setupTrackingContext.js:118:81
    at /var/task/node_modules/tailwindcss/lib/processTailwindFeatures.js:48:11
    at plugins (/var/task/node_modules/tailwindcss/lib/plugin.js:38:69)
    at LazyResult.runOnRoot (/var/task/node_modules/postcss/lib/lazy-result.js:357:16) {
  errno: -2,
  syscall: 'open',
  code: 'ENOENT',
  path: '/var/task/node_modules/tailwindcss/lib/css/preflight.css'
}

It appears the file node_modules\tailwindcss\lib\css\preflight.css is not being installed with the rest of TailwindCSS.

Tailwind is also invoked by Vite at build time and this apparently works successfully because all of the static CSS is generated the same as in my local production preview, it's only when this endpoint calls TailwindCSS programmatically by this api endpoint that the error occurs.

This is the structure of my package.json file:

{
    "name": "streaming-app",
    "version": "0.0.1",
    "private": true,
    "scripts": {
        "dev": "vite dev",
        "build": "vite build",
        "preview": "vite preview",
        "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
        "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
        "lint": "prettier --plugin-search-dir . --check . && eslint .",
        "format": "prettier --plugin-search-dir . --write .",
        "test": "vitest"
    },
    "devDependencies": {
        "@fullhuman/postcss-purgecss": "^5.0.0",
        "@sveltejs/adapter-auto": "^2.0.0",
        "@sveltejs/kit": "^1.20.4",
        "@testing-library/svelte": "^4.0.3",
        "@types/jest": "^29.5.3",
        "@types/node": "^20.5.7",
        "@types/obs-studio": "^2.17.0",
        "@typescript-eslint/eslint-plugin": "^5.45.0",
        "@typescript-eslint/parser": "^5.45.0",
        "eslint": "^8.28.0",
        "eslint-config-prettier": "^8.5.0",
        "eslint-plugin-svelte": "^2.30.0",
        "jsdom": "^22.1.0",
        "postcss-load-config": "^4.0.1",
        "prettier": "^2.8.0",
        "prettier-plugin-svelte": "^2.10.1",
        "supabase": "^1.82.2",
        "svelte": "^4.0.5",
        "svelte-check": "^3.4.3",
        "tslib": "^2.4.1",
        "typescript": "^5.0.0",
        "vite": "^4.4.2",
        "vite-plugin-tailwind-purgecss": "^0.1.3",
        "vitest": "^0.34.1",
        "vitest-svelte-kit": "^0.0.7"
    },
    "type": "module",
    "dependencies": {
        "@supabase/auth-helpers-sveltekit": "^0.10.2",
        "@supabase/supabase-js": "^2.33.1",
        "cssnano": "^6.0.1",
        "postcss": "^8.4.29",
        "tailwindcss": "^3.3.3",
        "autoprefixer": "^10.4.14",
        "@skeletonlabs/skeleton": "^2.0.0",
        "@skeletonlabs/tw-plugin": "^0.1.0",
        "@tailwindcss/typography": "^0.5.9"
    }
}

I only recently moved Tailwind, Postcss, Cssnano Skeleton, and Autoprefixer dependencies from dev dependencies to production dependencies as I just added the programmatic use of Tailwind as a feature. The local production build didn't have any issues running the endpoint with npm run preview but I figured they needed to be properly set as production dependencies to work after being deployed by Vercel.

I sued npm i {packagename} -P then ran npm upgrade and npm i --package-lock-only to try and make sure everything was generated correctly.

I would've expected that if anything, Vercel would just not automatically know to install Tailwind to be used on the server programmatically since it's normally only a part of the build process, but you can see from the stack trace that it's actually running the plugin function from inside Tailwind, it's only when it tries to import the file 'preflight.css' which is apparently missing.

EDIT:

Can you share your vite.config.js file?

// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';

export default defineConfig({
    plugins: [sveltekit()],
    test: {
        globals: true,
        environment: 'jsdom'
    }
});

More relevant config files:

// postcss.config.cjs
const tailwindcss = require('tailwindcss');
const autoprefixer = require('autoprefixer');

const config = {
    plugins: [
        //Some plugins, like tailwindcss/nesting, need to run before Tailwind,
        tailwindcss(),
        //But others, like autoprefixer, need to run after,
        autoprefixer
    ]
};

module.exports = config;
// tailwind.config.ts
import { join } from 'path'
import type { Config } from 'tailwindcss'
import { skeleton } from '@skeletonlabs/tw-plugin'
import typography from '@tailwindcss/typography'

const config = {
    darkMode: 'class',
    content: [
        './src/**/*.{html,js,svelte,ts}',
        join(require.resolve(
            '@skeletonlabs/skeleton'),
            '../**/*.{html,js,svelte,ts}'
        )
    ],
    theme: {
        extend: {},
    },
    plugins: [
        skeleton({
            themes: { preset: [ "skeleton" ] }
        }),
        typography
    ]
} satisfies Config;

export default config;

Note: I know it's relevant to how Vercel runs the build process, but just to be clear, this config doesn't get used in the API route in question:

// src/routes/api/css/+server.ts
import { json } from '@sveltejs/kit'
import type { RequestHandler } from '@sveltejs/kit'

import postcss from 'postcss'
import tailwindcss from 'tailwindcss'
import cssnano from 'cssnano'
import autoprefixer from 'autoprefixer'
import { skeleton } from '@skeletonlabs/tw-plugin'
import typography from '@tailwindcss/typography'

const sourceCSS = '@tailwind components; @tailwind utilities';

export const GET: RequestHandler = async ({ url }) => {
    const classNames = url.searchParams.get('css') ?? ''
    const rawContent = [{raw: classNames, extension: 'html'}]
    // use safelist instead of rawContent
    // pre-render on page load and cache against local storage

    const compiledCss = await postcss([
        tailwindcss({
            content: rawContent,
            plugins: [
                skeleton({themes: {preset: ["skeleton"]}}),
                typography
            ]
        }),
        autoprefixer(),
        cssnano()
    ]).process(sourceCSS)
    

    return json({output: compiledCss.css})
};
// svelte.config.js
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/kit/vite';

/** @type {import('@sveltejs/kit').Config} */
const config = {
    preprocess: [vitePreprocess({})],

    kit: {
        adapter: adapter()
    }
};

export default config;

and the global postcss file

// src/app.postcss
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind variants;

html, body { @apply h-full overflow-hidden; }

body {
    background-color: rgba(0, 0, 0, 0);
    font-family: 'helvetica'
}
input[type=text],input[type=password] {
    @apply input variant-form-material text-sm p-1 text-white;
}

:root {
    --nav-height: 30px;
}
#main-nav {
    height: var(--nav-height);
}
.faux-bg {
    @apply absolute w-[100vw] h-[100vh] left-0;
    top: var(--nav-height);
}


.layout-node-edit:not(:has(div:hover)):hover {
    outline: 2px solid gold;
}

.layout-node-active {
    outline: 4px solid gold;
}

Edit: I tried modifiying vite config as suggested below:

export default defineConfig({
    plugins: [sveltekit()],
    test: {
        globals: true,
        environment: 'jsdom'
    },
    css: {
        postcss,
    }
});

But it does not seem to have fixed the issue after redeploying.

Upvotes: 2

Views: 848

Answers (1)

keegancodes
keegancodes

Reputation: 43

I found this thread when faced with the same issue using Next. Since the build removes the CSS file before creating the function, the only solution here seems to be disabling the preflight entirely:

tailwindcss({
  content: [{ raw: html, extension: 'html' }],
  corePlugins: { preflight: false },
}),

This will solve the issue and allow the function to run, but if you rely on the preflight CSS, you'll need to account for it in some other way. Since the preflight CSS is a static file, you could take the result of the tailwind compilation and prepend the preflight CSS yourself to match what the output would have been.

Upvotes: 1

Related Questions