diff --git a/.changeset/eight-lemons-smile.md b/.changeset/eight-lemons-smile.md
new file mode 100644
index 000000000..06d361482
--- /dev/null
+++ b/.changeset/eight-lemons-smile.md
@@ -0,0 +1,5 @@
+---
+'houdini-react': patch
+---
+
+Stabilize react deployments
diff --git a/.changeset/yellow-tables-pump.md b/.changeset/yellow-tables-pump.md
new file mode 100644
index 000000000..d04ead999
--- /dev/null
+++ b/.changeset/yellow-tables-pump.md
@@ -0,0 +1,5 @@
+---
+'houdini-react': patch
+---
+
+Fix session redirects
diff --git a/e2e/react/houdini.config.js b/e2e/react/houdini.config.js
index 2441be158..0708c6951 100644
--- a/e2e/react/houdini.config.js
+++ b/e2e/react/houdini.config.js
@@ -27,11 +27,13 @@ const config = {
},
plugins: {
- 'houdini-react': {
- auth: {
- redirect: '/auth/token',
- sessionKeys: ['supersecret'],
- },
+ 'houdini-react': {},
+ },
+
+ router: {
+ auth: {
+ redirect: '/auth/token',
+ sessionKeys: ['supersecret'],
},
},
}
diff --git a/packages/create-houdini/templates/react-typescript/.meta.gitignore b/packages/create-houdini/templates/react-typescript/.meta.gitignore
index dfc458a61..80ee705de 100644
--- a/packages/create-houdini/templates/react-typescript/.meta.gitignore
+++ b/packages/create-houdini/templates/react-typescript/.meta.gitignore
@@ -7,13 +7,16 @@ yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
+.env.local
+.env.production.local
+.env.*.local
+
+
node_modules
dist
dist-ssr
*.local
-
-./$houdini
-./dist
+$houdini
# Editor directories and files
.vscode/*
diff --git a/packages/create-houdini/templates/react-typescript/src/+index.tsx b/packages/create-houdini/templates/react-typescript/src/+index.tsx
index 7ebaf4fae..d3b2ac928 100644
--- a/packages/create-houdini/templates/react-typescript/src/+index.tsx
+++ b/packages/create-houdini/templates/react-typescript/src/+index.tsx
@@ -1,47 +1,47 @@
-import React from "react";
+import React from 'react'
export default function App({ children }) {
- return (
-
-
-
-
-
-
- Houdini • React
-
-
- {children}
-
-
- );
+ return (
+
+
+
+
+
+
+ Houdini • React
+
+
+ {children}
+
+
+ )
}
-class ErrorBoundary extends React.Component {
- constructor(props) {
- super(props);
- this.state = { hasError: false };
- }
+class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean }> {
+ constructor(props: { children: React.ReactNode }) {
+ super(props)
+ this.state = { hasError: false }
+ }
- static getDerivedStateFromError(error) {
- return { hasError: true };
- }
+ static getDerivedStateFromError(error: Error) {
+ return { hasError: true }
+ }
- componentDidCatch(error, info) {
- console.error("ErrorBoundary caught an error:", error, info);
- }
+ componentDidCatch(error: Error, info: {}) {
+ console.error('ErrorBoundary caught an error:', error, info)
+ }
- render() {
- if (this.state.hasError) {
- return Something went wrong.
;
- }
- return this.props.children;
- }
+ render() {
+ if (this.state.hasError) {
+ return Something went wrong.
+ }
+ return this.props.children
+ }
}
diff --git a/packages/create-houdini/templates/react/.meta.gitignore b/packages/create-houdini/templates/react/.meta.gitignore
index dfc458a61..9436fbf45 100644
--- a/packages/create-houdini/templates/react/.meta.gitignore
+++ b/packages/create-houdini/templates/react/.meta.gitignore
@@ -7,6 +7,10 @@ yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
+.env.local
+.env.production.local
+.env.*.local
+
node_modules
dist
dist-ssr
diff --git a/packages/houdini-adapter-cloudflare/src/index.ts b/packages/houdini-adapter-cloudflare/src/index.ts
index 9d6aec536..880d430fd 100644
--- a/packages/houdini-adapter-cloudflare/src/index.ts
+++ b/packages/houdini-adapter-cloudflare/src/index.ts
@@ -3,13 +3,12 @@ import { fileURLToPath } from 'node:url'
const adapter: Adapter = async ({ adapterPath, outDir, sourceDir }) => {
// the first thing we have to do is copy the source directory over
- await fs.recursiveCopy(sourceDir, outDir)
+ await fs.recursiveCopy(sourceDir, path.join(outDir, 'assets'))
// read the contents of the worker file
let workerContents = (await fs.readFile(sourcePath('./worker.js')))!
- // if the project has a local schema, replace the schema import string with the
- // import
+ // make sure that the adapter module imports from the correct path
workerContents = workerContents.replaceAll('houdini/adapter', adapterPath)
await fs.writeFile(path.join(outDir, '_worker.js'), workerContents!)
diff --git a/packages/houdini-adapter-cloudflare/src/worker.ts b/packages/houdini-adapter-cloudflare/src/worker.ts
index 7e46b1c10..133c2be9a 100644
--- a/packages/houdini-adapter-cloudflare/src/worker.ts
+++ b/packages/houdini-adapter-cloudflare/src/worker.ts
@@ -9,7 +9,6 @@ const server_adapter = createServerAdapter({
const handlers: ExportedHandler = {
async fetch(req, env: any, ctx) {
- // if we aren't loading an asset, push the request through our router
const url = new URL(req.url).pathname
// we are handling an asset
diff --git a/packages/houdini-react/package.json b/packages/houdini-react/package.json
index a6a96d483..c155d3062 100644
--- a/packages/houdini-react/package.json
+++ b/packages/houdini-react/package.json
@@ -64,4 +64,4 @@
},
"main": "./build/plugin-cjs/index.js",
"types": "./build/plugin/index.d.ts"
-}
+}
\ No newline at end of file
diff --git a/packages/houdini-react/src/plugin/vite.tsx b/packages/houdini-react/src/plugin/vite.tsx
index 293a9b6e4..d76d3448c 100644
--- a/packages/houdini-react/src/plugin/vite.tsx
+++ b/packages/houdini-react/src/plugin/vite.tsx
@@ -1,4 +1,3 @@
-import { GraphQLSchema } from 'graphql'
import {
PluginHooks,
path,
@@ -6,13 +5,8 @@ import {
load_manifest,
isSecondaryBuild,
type ProjectManifest,
- type YogaServer,
type RouterManifest,
- localApiEndpoint,
- loadLocalSchema,
routerConventions,
- find_match,
- internalRoutes,
} from 'houdini'
import React from 'react'
import { build, type BuildOptions, type Connect } from 'vite'
@@ -117,11 +111,6 @@ export default {
},
async closeBundle(this, config) {
- // only build in production one
- if (isSecondaryBuild() || devServer) {
- return
- }
-
// tell the user what we're doing
console.log('🎩 Generating Server Assets...')
@@ -305,7 +294,21 @@ if (window.__houdini__nav_caches__ && window.__houdini__nav_caches__.artifact_ca
for (const header of Object.entries(result.headers ?? {})) {
res.setHeader(header[0], header[1])
}
- res.write(await result.text())
+ // handle redirects
+ if (result.status >= 300 && result.status < 400) {
+ res.writeHead(result.status, {
+ Location: result.headers.get('Location') ?? '',
+ ...[...result.headers.entries()].reduce(
+ (headers, [key, value]) => ({
+ ...headers,
+ [key]: value,
+ }),
+ {}
+ ),
+ })
+ } else {
+ res.write(await result.text())
+ }
res.end()
}
})
diff --git a/packages/houdini-react/src/runtime/server/renderToStream.ts b/packages/houdini-react/src/runtime/server/renderToStream.ts
index 565e24f2a..e31c3dbaa 100644
--- a/packages/houdini-react/src/runtime/server/renderToStream.ts
+++ b/packages/houdini-react/src/runtime/server/renderToStream.ts
@@ -1,12 +1,11 @@
import React from 'react'
-import type {
- renderToPipeableStream as RenderToPipeableStream,
- renderToReadableStream as RenderToReadableStream,
+import {
+ renderToPipeableStream,
+ renderToReadableStream,
} from 'react-dom/server'
import { createPipeWrapper, Pipe } from './renderToStream/createPipeWrapper'
import { createReadableWrapper } from './renderToStream/createReadableWrapper'
-import { nodeStreamModuleIsAvailable } from './renderToStream/loadNodeStreamModule'
import { resolveSeoStrategy, SeoStrategy } from './renderToStream/resolveSeoStrategy'
import { createDebugger } from './utils'
@@ -21,8 +20,8 @@ type Options = {
seoStrategy?: SeoStrategy
userAgent?: string
onBoundaryError?: (err: unknown) => void
- renderToReadableStream?: typeof RenderToReadableStream
- renderToPipeableStream?: typeof RenderToPipeableStream
+ renderToReadableStream?: typeof renderToReadableStream
+ renderToPipeableStream?: typeof renderToPipeableStream
}
type Result = (
| {
@@ -55,7 +54,7 @@ async function renderToStream(element: React.ReactNode, options: Options = {}):
const disable =
globalConfig.disable || (options.disable ?? resolveSeoStrategy(options).disableStream)
- const webStream = options.webStream ?? !(await nodeStreamModuleIsAvailable())
+ const webStream = true
debug(`disable === ${disable} && webStream === ${webStream}`)
let result: Result
@@ -80,7 +79,7 @@ async function renderToNodeStream(
options: {
debug?: boolean
onBoundaryError?: (err: unknown) => void
- renderToPipeableStream?: typeof RenderToPipeableStream
+ renderToPipeableStream?: typeof renderToPipeableStream
}
) {
debug('creating Node.js Stream Pipe')
@@ -109,12 +108,8 @@ async function renderToNodeStream(
}
})
}
- const renderToPipeableStream =
- options.renderToPipeableStream ??
- // @ts-ignore
- // We don't directly use import() because it shouldn't be bundled for Cloudflare Workers: the module react-dom/server.node contains a require('stream') which fails on Cloudflare Workers
- ((await import('react-dom/server.node'))
- .renderToPipeableStream as typeof RenderToPipeableStream)
+
+ console.log("THIS ->", renderToPipeableStream)
const { pipe: pipeOriginal } = renderToPipeableStream(element, {
onShellReady() {
@@ -160,7 +155,7 @@ async function renderToWebStream(
options: {
debug?: boolean
onBoundaryError?: (err: unknown) => void
- renderToReadableStream?: typeof RenderToReadableStream
+ renderToReadableStream?: typeof renderToReadableStream
}
) {
debug('creating Web Stream Pipe')
@@ -178,11 +173,6 @@ async function renderToWebStream(
}
})
}
- const renderToReadableStream =
- options.renderToReadableStream ??
- // We directly use import() because it needs to be bundled for Cloudflare Workers
- ((await import('react-dom/server.browser' as string))
- .renderToReadableStream as typeof RenderToReadableStream)
const readableOriginal = await renderToReadableStream(element, { onError })
const { allReady } = readableOriginal
diff --git a/packages/houdini/src/lib/config.ts b/packages/houdini/src/lib/config.ts
index 1cd8ee853..3de5fcb25 100644
--- a/packages/houdini/src/lib/config.ts
+++ b/packages/houdini/src/lib/config.ts
@@ -348,7 +348,7 @@ export class Config {
}
get routerBuildDirectory() {
- return path.join(this.projectRoot, 'dist', 'assets')
+ return path.join(this.projectRoot, 'dist')
}
get definitionsDocumentsPath() {
diff --git a/packages/houdini/src/lib/router/server.ts b/packages/houdini/src/lib/router/server.ts
index 103bad9f2..e032b628c 100644
--- a/packages/houdini/src/lib/router/server.ts
+++ b/packages/houdini/src/lib/router/server.ts
@@ -18,6 +18,9 @@ export function internalRoutes(config: ConfigFile): string[] {
}
export async function buildLocalSchema(config: Config): Promise {
+ // before we build the local schcema, we need to generate the typescript config file
+ // so that we can resolve all of the necessary imports
+
// load the current version of vite
const { build } = await import('vite')
@@ -32,15 +35,13 @@ export async function buildLocalSchema(config: Config): Promise {
input: {
schema: path.join(config.localApiDir, '+schema'),
},
- external: ['graphql'],
output: {
entryFileNames: 'assets/[name].js',
},
},
+ ssr: true,
lib: {
- entry: {
- schema: path.join(config.localApiDir, '+schema'),
- },
+ entry: path.join(config.localApiDir, '+schema'),
formats: ['es'],
},
},
diff --git a/packages/houdini/src/lib/types.ts b/packages/houdini/src/lib/types.ts
index ee15fd822..1e7ecbbaa 100644
--- a/packages/houdini/src/lib/types.ts
+++ b/packages/houdini/src/lib/types.ts
@@ -285,7 +285,7 @@ export type PluginHooks = {
buildStart?: (
this: PluginContext,
options: NormalizedInputOptions & { houdiniConfig: Config }
- ) => void
+ ) => void | Promise
buildEnd?: (
this: PluginContext,
diff --git a/packages/houdini/src/runtime/router/session.ts b/packages/houdini/src/runtime/router/session.ts
index 753288557..213aafda8 100644
--- a/packages/houdini/src/runtime/router/session.ts
+++ b/packages/houdini/src/runtime/router/session.ts
@@ -13,7 +13,6 @@ type ServerHandlerArgs = {
// so we want a single function that can be called to get the server
export async function handle_request(args: ServerHandlerArgs): Promise {
const plugin_config = args.config.router ?? {}
-
// if the project is configured to authorize users by redirect then
// we might need to set the session value
if (
@@ -25,18 +24,22 @@ export async function handle_request(args: ServerHandlerArgs): Promise {
+async function redirect_auth(args: ServerHandlerArgs): Promise {
// the session and configuration are passed as query parameters in
// the url
- const { searchParams } = new URL(args.url!, `http://${args.headers.get('host')}`)
+ const { searchParams, host } = new URL(args.url!, `http://${args.headers.get('host')}`)
const { redirectTo, ...session } = Object.fromEntries(searchParams.entries())
// encode the session information as a cookie in the response and redirect the user
- if (redirectTo) {
- const response = Response.redirect(redirectTo, 302)
- await set_session(args, response, session)
- return response
- }
+ const response = new Response('ok', {
+ status: 302,
+ headers: {
+ Location: redirectTo ?? '/',
+ },
+ })
+ await set_session(args, response, session)
+
+ return response
}
export type Server = {
diff --git a/packages/houdini/src/vite/houdini.ts b/packages/houdini/src/vite/houdini.ts
index 5558a8a9b..49b3a2e75 100644
--- a/packages/houdini/src/vite/houdini.ts
+++ b/packages/houdini/src/vite/houdini.ts
@@ -81,6 +81,10 @@ export default function Plugin(opts: PluginConfig = {}): VitePlugin {
// we use this to generate the final assets needed for a production build of the server.
// this is only called when bundling (ie, not in dev mode)
async closeBundle() {
+ if (isSecondaryBuild() || viteEnv.mode !== 'production' || devServer) {
+ return
+ }
+
for (const plugin of config.plugins) {
if (typeof plugin.vite?.closeBundle !== 'function') {
continue
@@ -89,15 +93,15 @@ export default function Plugin(opts: PluginConfig = {}): VitePlugin {
await plugin.vite!.closeBundle.call(this, config)
}
- if (isSecondaryBuild() || viteEnv.mode !== 'production' || devServer) {
- return
- }
-
// if we dont' have an adapter, we don't need to do anything
if (!opts.adapter) {
return
}
+ // dry
+ const outDir = config.routerBuildDirectory
+ const sourceDir = viteConfig.build.outDir
+
// tell the user what we're doing
console.log('🎩 Generating Deployment Assets...')
@@ -113,15 +117,20 @@ export default function Plugin(opts: PluginConfig = {}): VitePlugin {
// load the project manifest
const manifest = await load_manifest({ config, includeArtifacts: true })
+ // before we load the adapter we want to do some manual prep on the directories
+ // pull the ssr directory out of assets
+ await fs.recursiveCopy(path.join(sourceDir, 'ssr'), path.join(outDir, 'ssr'))
+ await fs.rmdir(path.join(sourceDir, 'ssr'))
+
// invoke the adapter
await opts.adapter({
config,
conventions: routerConventions,
- sourceDir: viteConfig.build.outDir,
+ sourceDir,
publicBase: viteConfig.base,
- outDir: config.routerBuildDirectory,
+ outDir,
manifest,
- adapterPath: './assets/ssr/entries/adapter',
+ adapterPath: './ssr/entries/adapter',
})
// if there is a public directory at the root of the project,
@@ -142,7 +151,7 @@ export default function Plugin(opts: PluginConfig = {}): VitePlugin {
}
// @ts-expect-error
- plugin.vite!.buildStart.call(this, {
+ await plugin.vite!.buildStart.call(this, {
...args,
houdiniConfig: config,
})