kiliman / remix-express-vite-plugin

This package includes a Vite plugin to use in your Remix app. It configures an Express server for both development and production using TypeScript.
118 stars 6 forks source link

Run Next and Remix on the same server #13

Closed xHomu closed 4 months ago

xHomu commented 4 months ago

I'm trying to migrate an existing express app that have some additional nextjs routes, based on sergio's article.

It works as a stand-alone express.ts server, but I couldn't quite get it working with this plugin, am I incorrect to insert the next paths in configure?

Reproduction: https://github.com/xHomu/remix-express-vite-plugin/tree/add-next-routes/example/remix-vite-express

navigating to localhost:3000/my-route returns a Remix route-not-found error: image

// This creates an express server that runs inside vite
export const app = createExpressApp({
  configure: async app => {
    // customize your express app with additional middleware
    app.use(compression())
    app.disable('x-powered-by')
    app.use(morgan('tiny'))

    // Initiate Next Handler
    const nextApp = next({ dev: true })
    const handle = nextApp.getRequestHandler()
    await nextApp.prepare()

    console.log('Next.js is ready')

    // @ts-expect-error test yadayada
    function nextHandler(req, res) {
      return handle(req, res)
    }

    app.all('/my-route', nextHandler)
    app.all('/_next/*', nextHandler)
  },

https://github.com/xHomu/remix-express-vite-plugin/blob/add-next-routes/example/remix-vite-express/remix-app/app.server.ts#L16

Just in case, I tested the route on a vanilla express server via npm run dev-next: node server.js and localhost:3000/my-route worked.

kiliman commented 4 months ago

Hmm... it should work. I'll see if I can figure it out. Everything from configure is added to the express app first, followed by static and remix build assets, then finally the remix request handler.

As long as your middleware handler doesn't call next, it should not fall through to remix.

kiliman commented 4 months ago

This works, so looks like something with the real next handler. I'll keep checking.

export const app = createExpressApp({
  configure: app => {
    // customize your express app with additional middleware
    app.use(morgan('tiny'))

    function nextHandler(req: any, res: any) {
      res.send('hello from next: ' + req.url)
    }

    app.all('/my-route', nextHandler)
    app.all('/_next/*', nextHandler)
  },
})
image image image
kiliman commented 4 months ago

OK, I commented out where I set up the remix handler, and the next app ran just fine inside my vite plugin. I just need to figure out why it's falling through after the next handler executes.

image image
kiliman commented 4 months ago

Ok, I figured out the issue. You made your configure function async, which was not expected.

I moved the next app initialization outside of configure, and it works now.

Let me think if I want to support async configure. But this should work now.

import morgan from 'morgan'
import { createExpressApp } from 'remix-create-express-app'
import { sayHello } from './hello.server'
import { AppLoadContext } from '@remix-run/node'
import compression from 'compression'
import next from 'next'

console.log('initializing Next.js')
const nextApp = next({ dev: true })
const handle = nextApp.getRequestHandler()
await nextApp.prepare()
console.log('Next.js is ready')

// This creates an express server that runs inside vite
export const app = createExpressApp({
  configure: app => {
    // customize your express app with additional middleware
    app.use(compression())
    app.disable('x-powered-by')
    app.use(morgan('tiny'))

    // @ts-expect-error test yadayada
    function nextHandler(req, res) {
      console.log('handling next route', req.url)
      return handle(req, res)
    }

    console.log('setting up next routes')
    app.all('/my-route', nextHandler)
    app.all('/_next/*', nextHandler)
  },
  getLoadContext: () => {
    // return the AppLoadContext
    return { sayHello } as AppLoadContext
  },
  unstable_middleware: true,
})
image
kiliman commented 4 months ago

Note, if you have any links in your Remix app to your Next app, make sure you either use regular <a href> tags or <Link to reloadDocument> to ensure that React Router doesn't try to process the link.

<ul>
  <li>
    <Link to="/test">Test</Link>
  </li>
  <li>
    <Link to="/my-route" reloadDocument>
      Next.js /my-route
    </Link>
  </li>
</ul>
xHomu commented 4 months ago

Ah, async functions reared their ugly head again! I was worried about the next.prepare function not running.

kiliman commented 4 months ago

Is it working for you now with the next app setup outside your configure function?

xHomu commented 4 months ago

Can confirm that moving the async functions out fixed the issue! image