How to access public assets on both server and client?

wasp version: 0.15.1 Hey all, I'm currently working on a project where I need to assign Lottie animations files randomly when an entry is created. I had thought I had solved it, but it doesn't work after deploying to the test environment. I set up a server setupFn that searches the file system (/public/animations) for .lottie files and stores them. Similar to this: https://wasp-lang.dev/docs/project/server-config#storing-some-values-for-later-use. However, when built, the setupFn either doesn't run or can't find the files. They are in the same location in both the .wasp/out and .wasp/build directories. I need these filenames available for both an action and the client. This is my setupFn:
import { readdir } from "node:fs/promises";
import path from "node:path";
import type { ServerSetupFn } from "wasp/server";

let animationFilenames: string[] | undefined = undefined;

// get all filenames from the `public/animations` directory
const _getAnimationFilenames = async () => {
try {
const animationFilenames = await readdir(
path.resolve("./../../web-app/public/animations"),
"utf8",
);
console.log(`🔎 ${animationFilenames.length || 0} animations found`);
// get only .lottie files
return animationFilenames.filter((animationFilename) =>
animationFilename.endsWith(".lottie"),
);
} catch {
return [];
}
};

export const serverSetup: ServerSetupFn = async () => {
animationFilenames = await _getAnimationFilenames();
};

// store filenames for later use - https://wasp-lang.dev/docs/project/server-config#storing-some-values-for-later-use
export const getAnimationFilenames = () => animationFilenames;
import { readdir } from "node:fs/promises";
import path from "node:path";
import type { ServerSetupFn } from "wasp/server";

let animationFilenames: string[] | undefined = undefined;

// get all filenames from the `public/animations` directory
const _getAnimationFilenames = async () => {
try {
const animationFilenames = await readdir(
path.resolve("./../../web-app/public/animations"),
"utf8",
);
console.log(`🔎 ${animationFilenames.length || 0} animations found`);
// get only .lottie files
return animationFilenames.filter((animationFilename) =>
animationFilename.endsWith(".lottie"),
);
} catch {
return [];
}
};

export const serverSetup: ServerSetupFn = async () => {
animationFilenames = await _getAnimationFilenames();
};

// store filenames for later use - https://wasp-lang.dev/docs/project/server-config#storing-some-values-for-later-use
export const getAnimationFilenames = () => animationFilenames;
Am I doing something wrong? Does any one have any other ideas to accomplish the same result? And possibly, any tips on debugging the setupFn during the build process? Thanks, Justin
9 Replies
kapa.ai
kapa.ai•3mo ago
Hi @Justin Vandenberg. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Thank you for your question about accessing public assets on both server and client in Wasp. I'll do my best to address your concerns based on the provided knowledge sources. The issue you're experiencing seems to be related to how static assets are handled in Wasp and how the server setup function works. Let's break this down: 1. Static Assets Handling: Wasp has a specific way of handling static assets. According to the Static Asset Handling documentation (https://wasp-lang.dev/docs/project/static-assets#the-public-directory), assets placed in the public directory at the root of your project will be served at the root path / during development and copied to the root of the dist directory as-is. However, it's important to note that assets in the public directory cannot be imported from JavaScript code. 2. Server Setup Function: The server setup function, as described in the Server Config documentation (https://wasp-lang.dev/docs/project/server-config#api-reference), is executed on server start and allows you to do custom setup. However, it's important to understand that this function runs in the server environment, which may not have direct access to the client-side file system after deployment. Given these points, here are some suggestions and explanations: 1. File Access: Your current approach of reading files from the server's file system may not work reliably in a production environment. When deployed, the server and client parts of your application are typically separated, and the server may not have direct access to the client's public directory. 2. Alternative Approach: Instead of trying to read the files from the server, consider the following approach: a. On the client side, you can create a JSON file (e.g., animations.json) in the public directory that contains a list of all your animation filenames.
b. In your client-side code, you can fetch this JSON file and use it to get the list of animations.
c. For server-side operations, you can either: - Pass the selected animation filename from the client to the server when needed. - Or, duplicate the animations.json file in a location accessible by the server code and read it during the server setup. 3. Debugging: For debugging the setupFn during the build process, you can add more detailed logging statements. However, keep in mind that the build environment might be different from your local development environment. Here's a modified version of your setup function that might work better:
import fs from 'fs/promises';
import path from 'path';
import type { ServerSetupFn } from "wasp/server";

let animationFilenames: string[] | undefined = undefined;

const _getAnimationFilenames = async () => {
try {
// Read from a JSON file instead of directory
const data = await fs.readFile(path.resolve(__dirname, 'animations.json'), 'utf8');
const animations = JSON.parse(data);
console.log(`🔎 ${animations.length || 0} animations found`);
return animations.filter((animationFilename: string) => animationFilename.endsWith(".lottie"));
} catch (error) {
console.error('Error reading animations:', error);
return [];
}
};

export const serverSetup: ServerSetupFn = async () => {
animationFilenames = await _getAnimationFilenames();
};

export const getAnimationFilenames = () => animationFilenames;
import fs from 'fs/promises';
import path from 'path';
import type { ServerSetupFn } from "wasp/server";

let animationFilenames: string[] | undefined = undefined;

const _getAnimationFilenames = async () => {
try {
// Read from a JSON file instead of directory
const data = await fs.readFile(path.resolve(__dirname, 'animations.json'), 'utf8');
const animations = JSON.parse(data);
console.log(`🔎 ${animations.length || 0} animations found`);
return animations.filter((animationFilename: string) => animationFilename.endsWith(".lottie"));
} catch (error) {
console.error('Error reading animations:', error);
return [];
}
};

export const serverSetup: ServerSetupFn = async () => {
animationFilenames = await _getAnimationFilenames();
};

export const getAnimationFilenames = () => animationFilenames;
Remember to create an animations.json file in your server source directory with the list of animation filenames. Lastly, for accessing these files on the client side, you can create a similar function that fetches the animations.json file from the public URL. I hope this helps address your issue. If you need further clarification or have additional questions, please feel free to ask. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @Justin Vandenberg, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
Justin Vandenberg
Justin VandenbergOP•3mo ago
I did try something similar to the json approach the bot mentioned, but the json file doesn't seem to be complied and/or can't be resolved.
martinsos
martinsos•3mo ago
@Justin Vandenberg The issue is that you are getting those files from web-app. Web-app and server are separate things, and when deployed, server doesn't have access to the web-app. Web app is deployed as a bunch of static files, while server you will typicaly deploy as a docker image, and none have access to the other. This works in dev only because they are next to each other in build directory. How about you just put animations in the src/animations dir? That one will get embedded into both web-app and server, and you should be able to access it both in development and in production.
Justin Vandenberg
Justin VandenbergOP•3mo ago
I don't see the directory being compiled in the the out directory (it does appear in the build directory). Correct me if I'm wrong, but that would only work when built, right?
martinsos
martinsos•3mo ago
src/animations? Hm yeah, so in dev we don't copy it, in build we do, but in any case you should be able to import stuff in src into another file in src. Ok, you need to read a file, not import, but those files are in both cases next to each other. The questio is, when you do "./../../ ....", waht is that . realtive to? If it is current file from which you are calling this, then it should work. If it is instead relative to from where server is being executed, then it will be an issue. So you should, if you can (and I think yoshould), make sure that you are using that path in such way it is relative to the location of file you are writing this from. __dirname might give you that (I see GPT also used it above).
Justin Vandenberg
Justin VandenbergOP•3mo ago
I'll have to see if I can get that working. I think your approach would be better if I can get that path ironed out. It looks like the paths are originating in .wasp/out/server/bundle in dev. The work around I got working today was to copy those files when the Docker container is being built. I've got this in my user-defined Dockerfile.
COPY web-app/public/animations /app/.wasp/build/web-app/public/animations
COPY web-app/public/animations /app/.wasp/build/web-app/public/animations
martinsos
martinsos•3mo ago
Yup, that can also work, nice job! So just to summarize: I would put animations under src, so src/animations, and then also I would reference them while making sure to use __dirname. Unless I got wrong what __dirname does, this should work.
Justin Vandenberg
Justin VandenbergOP•3mo ago
Thank you! I'll report back here with my findings for future reference.
MEE6
MEE6•3mo ago
Wohooo @Justin Vandenberg, you just became a Waspeteer level 1!

Did you find this page helpful?