Giter Site home page Giter Site logo

jonluca / vite-typescript-ssr-react Goto Github PK

View Code? Open in Web Editor NEW
340.0 5.0 46.0 3.15 MB

🚀 A Vite Typescript SSR React boilerplate!

Home Page: https://blog.jonlu.ca/posts/vite

JavaScript 3.94% HTML 10.92% TypeScript 83.40% CSS 1.74%
vite typescript react ssr express javascript

vite-typescript-ssr-react's Introduction

Vite Typescript React 18 SSR

Node CI

A blazingly modern web development stack. This template repo tries to achieve the minimum viable example for each of the following:

video

Development

yarn
yarn dev:server

That should start the server. It will open to http://localhost:7456.

If you'd like to just develop the UI, you can use

yarn
yarn dev:client

To start the native vite client.

Building

yarn build
yarn serve

yarn build will create the assets in dist - a client and server folder. Serve will run dist/server.js with Node, but feel free to change this to use Docker or some other process manager to suit your deployment needs.

Files

eslintrc.js - a barebones eslint configuration for 2021, that extends off of the recommended ESLint config and prettier

.prettierrc.js - the prettier config

index.html - the vite entrypoint, that includes the entry point for the client

postcss.config.cjs - CommonJS module that defines the PostCSS config

server.ts - The barebones Express server with logic for SSRing Vite pages

tailwind.config.cjs - CommonJS module that defines the Tailwind config

tsconfig.json - TypeScript configuration

vite.config.ts - Vite configuration

CI

We use GitHub actions to build the app. The badge is at the top of the repo. Currently it just confirms that everything builds properly.

vite-typescript-ssr-react's People

Contributors

dependabot[bot] avatar jonluca avatar laishuxin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

vite-typescript-ssr-react's Issues

Error on production

how to fix this?

i have added this on line no 64 on server.ts file
app.use('/api', getApi)

this is the error
image

SSR does not trigger page reload when change

When serving the application using dev:server, changes do not update/reload the page. I can see the log from the server as follow, but the page does not reload automatically.

12:41:08 [vite] page reload src/client/pages/Main.tsx

[Bug] [Error: ENOENT: no such file or directory, scandir]

Hello, i hope you are good. I want to try improve this project with research about ssr, I like this template

But got some issues when running the server or client. You can see at this screenshot

Screenshot 2022-08-08 at 11 10 54

How the solve this issue? I'm using node engine 14.17.0

Running without ts-node?

Is it possible to run this without ts-node in production? I.e. pre-transpile all the Typescript up-front.

Vite React MUI SSR Error - Expected server HTML to contain a matching <div> in <div>

I am trying to integrate Vite SSR with MUI and I am facing warning and errors when I run the "dev:server" command.

Expected server HTML to contain a matching

in

Hydration failed because the initial UI does not match what was rendered on the server.

This is my codebase

entry-client.tsx

import * as React from "react";
import * as ReactDOM from "react-dom/client";
import { createBrowserRouter, matchRoutes, RouterProvider } from "react-router-dom";

import { routes } from "./App";
import createEmotionCache from "../../createEmotionCache";
import { CacheProvider } from "@emotion/react";
import { theme } from "../../theme";
import { ThemeProvider } from "@mui/material";
const cache = createEmotionCache();

async function hydrate() {
  // Determine if any of the initial routes are lazy
  let lazyMatches = matchRoutes(routes, window.location)?.filter(m => m.route.lazy);

  // Load the lazy matches and update the routes before creating your router
  // so we can hydrate the SSR-rendered content synchronously
  if (lazyMatches && lazyMatches?.length > 0) {
    await Promise.all(
      lazyMatches.map(async m => {
        let routeModule = await m.route.lazy!();
        Object.assign(m.route, { ...routeModule, lazy: undefined });
      }),
    );
  }

  let router = createBrowserRouter(routes);

  ReactDOM.hydrateRoot(
    document.getElementById("app")!,
    <React.StrictMode>
      <CacheProvider value={cache}>
        <ThemeProvider theme={theme()}>
          <RouterProvider router={router} fallbackElement={null} />
        </ThemeProvider>
      </CacheProvider>
    </React.StrictMode>,
  );
}

hydrate();

entry-server.tsx

import type * as express from "express";
import * as React from "react";
import ReactDOMServer from "react-dom/server";
import { createStaticHandler, createStaticRouter, StaticRouterProvider } from "react-router-dom/server";
import { routes } from "./App";
import createEmotionCache from "../../createEmotionCache";
import createEmotionServer from "@emotion/server/create-instance";
import { CssBaseline, ThemeProvider } from "@mui/material";
import { CacheProvider } from "@emotion/react";
import { theme } from "../../theme";

export function createFetchRequest(req: express.Request): Request {
  let origin = `${req.protocol}://${req.get("host")}`;
  // Note: This had to take originalUrl into account for presumably vite's proxying
  let url = new URL(req.originalUrl || req.url, origin);

  let controller = new AbortController();
  req.on("close", () => controller.abort());

  let headers = new Headers();

  for (let [key, values] of Object.entries(req.headers)) {
    if (values) {
      if (Array.isArray(values)) {
        for (let value of values) {
          headers.append(key, value);
        }
      } else {
        headers.set(key, values);
      }
    }
  }

  let init: RequestInit = {
    method: req.method,
    headers,
    signal: controller.signal,
  };

  if (req.method !== "GET" && req.method !== "HEAD") {
    init.body = req.body;
  }

  return new Request(url.href, init);
}

export async function render(request: express.Request) {
  const cache = createEmotionCache();
  const { extractCriticalToChunks, constructStyleTagsFromChunks } = createEmotionServer(cache);
  let { query, dataRoutes } = createStaticHandler(routes);
  let remixRequest = createFetchRequest(request);
  let context = await query(remixRequest);

  if (context instanceof Response) {
    throw context;
  }

  let router = createStaticRouter(dataRoutes, context);
  const html = ReactDOMServer.renderToString(
    <CacheProvider value={cache}>
      <ThemeProvider theme={theme(request)}>
        {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
        <CssBaseline />
        <StaticRouterProvider router={router} context={context} nonce="the-nonce" />
      </ThemeProvider>
    </CacheProvider>,
  );

  const emotionChunks = extractCriticalToChunks(html);
  const emotionCss = constructStyleTagsFromChunks(emotionChunks);

  return {
    html,
    css: emotionCss,
  };
}

server.ts

import type { Request, Response, NextFunction } from "express";
import fs from "fs/promises";
import path from "path";
import express from "express";
import compression from "compression";
import serveStatic from "serve-static";
import { createServer as createViteServer } from "vite";
const isTest = process.env.NODE_ENV === "test" || !!process.env.VITE_TEST_BUILD;

const resolve = (p: string) => path.resolve(__dirname, p);

const getStyleSheets = async () => {
  try {
    const assetpath = resolve("dist/assets");
    const files = await fs.readdir(assetpath);
    const cssAssets = files.filter(l => l.endsWith(".css"));
    const allContent = [];
    for (const asset of cssAssets) {
      const content = await fs.readFile(path.join(assetpath, asset), "utf-8");
      allContent.push(`<style type="text/css">${content}</style>`);
    }
    return allContent.join("\n");
  } catch {
    return "";
  }
};

async function createServer(isProd = process.env.NODE_ENV === "production") {
  const app = express();
  // Create Vite server in middleware mode and configure the app type as
  // 'custom', disabling Vite's own HTML serving logic so parent server
  // can take control
  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: "custom",
    logLevel: isTest ? "error" : "info",
  });

  // use vite's connect instance as middleware
  // if you use your own express router (express.Router()), you should use router.use
  app.use(vite.middlewares);
  const requestHandler = express.static(resolve("assets"));
  app.use(requestHandler);
  app.use("/assets", requestHandler);

  if (isProd) {
    app.use(compression());
    app.use(
      serveStatic(resolve("dist/client"), {
        index: false,
      }),
    );
  }
  const stylesheets = getStyleSheets();
  app.use("*", async (req: Request, res: Response, next: NextFunction) => {
    const url = req.originalUrl;

    try {
      // 1. Read index.html
      let template = await fs.readFile(isProd ? resolve("dist/client/index.html") : resolve("index.html"), "utf-8");

      // 2. Apply Vite HTML transforms. This injects the Vite HMR client, and
      //    also applies HTML transforms from Vite plugins, e.g. global preambles
      //    from @vitejs/plugin-react
      template = await vite.transformIndexHtml(url, template);

      // 3. Load the server entry. vite.ssrLoadModule automatically transforms
      //    your ESM source code to be usable in Node.js! There is no bundling
      //    required, and provides efficient invalidation similar to HMR.
      let productionBuildPath = path.join(__dirname, "./dist/server/entry-server.mjs");
      let devBuildPath = path.join(__dirname, "./src/client/entry-server.tsx");
      const { render } = await vite.ssrLoadModule(isProd ? productionBuildPath : devBuildPath);

      // 4. render the app HTML. This assumes entry-server.js's exported `render`
      //    function calls appropriate framework SSR APIs,
      //    e.g. ReactDOMServer.renderToString()
      const appHtml = await render(req);
      console.log('appHtml', appHtml)
      const cssAssets = appHtml.css;

      // 5. Inject the app-rendered HTML into the template.
      const html = template.replace(`<!--app-html-->`, appHtml.html).replace(`<!--head-->`, cssAssets);

      // 6. Send the rendered HTML back.
      res.status(200).set({ "Content-Type": "text/html" }).end(html);
    } catch (e: any) {
      !isProd && vite.ssrFixStacktrace(e);
      console.log(e.stack);
      // If an error is caught, let Vite fix the stack trace so it maps back to
      // your actual source code.
      vite.ssrFixStacktrace(e);
      next(e);
    }
  });
  const port = process.env.PORT || 7456;
  app.listen(Number(port), "0.0.0.0", () => {
    console.log(`App is listening on http://localhost:${port}`);
  });
}

createServer();

enter image description here

For some reason, I am unable to run "dev:server" on stackblitz.

Here is the link to Stackblitz: https://codesandbox.io/p/github/arunmmanoharan/vite-ssr-sample

Here is the link to the github repo: https://github.com/arunmmanoharan/vite-ssr-sample

Please advice.

dist/src/server/routes/api not found

How to reproduce:

  1. Clone this repo
  2. Yarn install
  3. yarn serve

you get this error:

yarn run v1.22.17
$ yarn build && cross-env NODE_ENV=production node --experimental-modules dist/server.js
$ tsc -p tsconfig.prod.json && yarn build:client && yarn build:server && yarn copy-files
$ vite build --outDir dist/client
vite v2.8.1 building for production...
✓ 32 modules transformed.
dist/client/index.html                  0.65 KiB
dist/client/assets/index.1305d8da.js    3.58 KiB / gzip: 1.34 KiB
dist/client/assets/index.3ad06a7e.css   9.59 KiB / gzip: 3.19 KiB
dist/client/assets/vendor.7cb176a6.js   202.63 KiB / gzip: 50.71 KiB
$ vite build --ssr src/client/entry-server.tsx --outDir dist/server
vite v2.8.1 building SSR bundle for production...
✓ 6 modules transformed.
dist/server/entry-server.js   3.11 KiB
$ copyfiles static/* dist/assets && copyfiles index.html dist
internal/process/esm_loader.js:74
    internalBinding('errors').triggerUncaughtException(
                              ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/ondrejrohon/Desktop/react-tailwind-express-typescript/vite-typescript-ssr-react/dist/src/server/routes/api' imported from /Users/ondrejrohon/Desktop/react-tailwind-express-typescript/vite-typescript-ssr-react/dist/server.js
    at finalizeResolution (internal/modules/esm/resolve.js:271:11)
    at moduleResolve (internal/modules/esm/resolve.js:694:10)
    at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:805:11)
    at Loader.resolve (internal/modules/esm/loader.js:88:40)
    at Loader.getModuleJob (internal/modules/esm/loader.js:241:28)
    at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:72:40)
    at link (internal/modules/esm/module_job.js:71:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Thanks for looking into this and for making this starter. Looks amazing!

Failed to load asset when serving

Hi, when doing yarn run serve in a new codespace with the template, I get a Failed to load url /assets/index-396a04c2.js (resolved id: /assets/index-396a04c2.js). Does the file exist? error in the console. This file does exist in dist/assets so I don’t know what it is about.

Update head tags using Helmet

I'm using react-helmet to update the Head tags but the built HTML source (ctrl + u) does not reflect this. However, if you inspect element (ctrl + shift + i) and look at the source, the head is updated well.

// server.ts
// here we only update the html, not the head tags
const { render } = await vite.ssrLoadModule(isProd ? productionBuildPath : devBuildPath);
...
const html = template.replace(`<!--app-html-->`, appHtml)

Any ideas on how to update the head tags as well?

I meet a problem

I want to use ssr but when I send http request, then the page is not Rendering of my server datas, for example I send a request to get dog img but page shows <img src=""/>. I expect a result is <img src="http://xxxx.jpg">. So Can you provide a example?

Dev dependecies in Docker

So when I try to build a docker image with this

FROM node:20-bookworm-slim as builder

WORKDIR /app

COPY package.json yarn.lock ./

RUN yarn install --no-cache --frozen-lockfile

ENV NODE_ENV=production


COPY . .

RUN yarn build && yarn install --no-cache  --ignore-scripts --prefer-offline



FROM node:20-bookworm-slim AS runner

WORKDIR /app

RUN addgroup --gid 1001 --system nodejs

COPY --chown=1001:1001 --from=builder /app /app

USER favdog

ENV NODE_ENV=production

CMD ["node", "./dist/server.js"]

Nothing to fancy I would say - but am getting error on the dev dependecnies not present in the is it becasue the docker should contain just the files builded by vite ?
but inside the server.ts there is also used vite - createServer
which is also rightfully dev dependency ?

I wanna run docker image in prod mode

Question : Can i accesing token with cookie?

Hello, i want to ask can i accesing cookie data in some page for example i want to check for cookie token and if exist you can stay and otherwise you will be redirected into login page. This like initialProps nextjs or serverSideProps

Or other i want to fetching data in server side?
Can you give me a example please?

Could not find a declaration file for module 'serve-static'.


> [email protected] build /Users/petrus/Code/my-project
> rimraf dist && tsc -p tsconfig.prod.json && yarn build:client && yarn build:server && yarn copy-files

server.ts:6:25 - error TS7016: Could not find a declaration file for module 'serve-static'. '/Users/petrus/Code/olarm/olarm-directory/node_modules/.pnpm/[email protected]/node_modules/serve-static/index.js' implicitly has an 'any' type.
  If the 'serve-static' package actually exposes this module, consider sending a pull request to amend 'https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/serve-static'

6 import serveStatic from "serve-static";
                          ~~~~~~~~~~~~~~

src/__tests__/demo.test.tsx:12:31 - error TS2339: Property 'toBeInTheDocument' does not exist on type 'Assertion<HTMLElement>'.

12     expect(getByText("Repo")).toBeInTheDocument();
                                 ~~~~~~~~~~~~~~~~~


Found 2 errors in 2 files.

Errors  Files
     1  server.ts:6
     1  src/__tests__/demo.test.tsx:12
 ELIFECYCLE  Command failed with exit code 2.

Crash on start yarn dev:server

➜  vite-typescript-ssr-react git:(master) yarn dev:server
yarn run v1.22.19
$ nodemon --watch server.ts --watch src/server --exec 'ts-node server.ts'
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): server.ts src/server/**/*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `ts-node server.ts`
App is listening on http://localhost:7456
[Error: ENOENT: no such file or directory, scandir '/home/dev/test/vite-typescript-ssr-react/dist/assets'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'scandir',
  path: '/home/dev/test/vite-typescript-ssr-react/dist/assets'
}

I'm guessing you have to build first to create the dist/assets folder?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.