diff --git a/.gitignore b/.gitignore
index 944c89d..b22eeb6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,4 +40,4 @@ Thumbs.db
# Next.js
.next
-*.local
+*.local
\ No newline at end of file
diff --git a/apps/demo-nextjs-app-router/.eslintrc.json b/apps/demo-nextjs-app-router/.eslintrc.json
new file mode 100644
index 0000000..17b0bc9
--- /dev/null
+++ b/apps/demo-nextjs-app-router/.eslintrc.json
@@ -0,0 +1,40 @@
+{
+ "extends": [
+ "plugin:@nx/react-typescript",
+ "next",
+ "next/core-web-vitals",
+ "../../.eslintrc.json"
+ ],
+ "ignorePatterns": ["!**/*", ".next/**/*"],
+ "overrides": [
+ {
+ "files": ["*.*"],
+ "rules": {
+ "@next/next/no-html-link-for-pages": "off"
+ }
+ },
+ {
+ "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+ "rules": {
+ "@next/next/no-html-link-for-pages": [
+ "error",
+ "apps/demo-nextjs-app-router/pages"
+ ]
+ }
+ },
+ {
+ "files": ["*.ts", "*.tsx"],
+ "rules": {}
+ },
+ {
+ "files": ["*.js", "*.jsx"],
+ "rules": {}
+ },
+ {
+ "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"],
+ "env": {
+ "jest": true
+ }
+ }
+ ]
+}
diff --git a/apps/demo-nextjs-app-router/app/api/fal/proxy/route.ts b/apps/demo-nextjs-app-router/app/api/fal/proxy/route.ts
new file mode 100644
index 0000000..998ab54
--- /dev/null
+++ b/apps/demo-nextjs-app-router/app/api/fal/proxy/route.ts
@@ -0,0 +1,3 @@
+import { route } from '@fal-ai/serverless-proxy/nextjs';
+
+export const { GET, POST } = route;
diff --git a/apps/demo-nextjs-app/pages/styles.css b/apps/demo-nextjs-app-router/app/global.css
similarity index 100%
rename from apps/demo-nextjs-app/pages/styles.css
rename to apps/demo-nextjs-app-router/app/global.css
diff --git a/apps/demo-nextjs-app-router/app/layout.tsx b/apps/demo-nextjs-app-router/app/layout.tsx
new file mode 100644
index 0000000..836b8f4
--- /dev/null
+++ b/apps/demo-nextjs-app-router/app/layout.tsx
@@ -0,0 +1,18 @@
+import './global.css';
+
+export const metadata = {
+ title: 'Welcome to demo-nextjs-app-router',
+ description: 'Generated by create-nx-workspace',
+};
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
{children}
+
+ );
+}
diff --git a/apps/demo-nextjs-app/pages/index.module.css b/apps/demo-nextjs-app-router/app/page.module.css
similarity index 100%
rename from apps/demo-nextjs-app/pages/index.module.css
rename to apps/demo-nextjs-app-router/app/page.module.css
diff --git a/apps/demo-nextjs-app-router/app/page.tsx b/apps/demo-nextjs-app-router/app/page.tsx
new file mode 100644
index 0000000..2313dd1
--- /dev/null
+++ b/apps/demo-nextjs-app-router/app/page.tsx
@@ -0,0 +1,170 @@
+'use client';
+
+import * as fal from '@fal-ai/serverless-client';
+import { useMemo, useState } from 'react';
+
+// @snippet:start(client.config)
+fal.config({
+ requestMiddleware: fal.withProxy({
+ targetUrl: '/api/fal/proxy', // the built-int nextjs proxy
+ // targetUrl: 'http://localhost:3333/api/_fal/proxy', // or your own external proxy
+ }),
+});
+// @snippet:end
+
+// @snippet:start(client.result.type)
+type Image = {
+ url: string;
+ file_name: string;
+ file_size: number;
+};
+type Result = {
+ images: Image[];
+};
+// @snippet:end
+
+type ErrorProps = {
+ error: any;
+};
+
+function Error(props: ErrorProps) {
+ if (!props.error) {
+ return null;
+ }
+ return (
+
+ Error {props.error.message}
+
+ );
+}
+
+const DEFAULT_PROMPT =
+ 'a city landscape of a cyberpunk metropolis, raining, purple, pink and teal neon lights, highly detailed, uhd';
+
+export default function Home() {
+ // @snippet:start("client.ui.state")
+ // Input state
+ const [prompt, setPrompt] = useState(DEFAULT_PROMPT);
+ // Result state
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [result, setResult] = useState(null);
+ const [logs, setLogs] = useState([]);
+ const [elapsedTime, setElapsedTime] = useState(0);
+ // @snippet:end
+ const image = useMemo(() => {
+ if (!result) {
+ return null;
+ }
+ return result.images[0];
+ }, [result]);
+
+ const reset = () => {
+ setLoading(false);
+ setError(null);
+ setResult(null);
+ setLogs([]);
+ setElapsedTime(0);
+ };
+
+ const generateImage = async () => {
+ reset();
+ // @snippet:start("client.queue.subscribe")
+ setLoading(true);
+ const start = Date.now();
+ try {
+ const result: Result = await fal.subscribe('110602490-lora', {
+ input: {
+ prompt,
+ model_name: 'stabilityai/stable-diffusion-xl-base-1.0',
+ image_size: 'square_hd',
+ },
+ pollInterval: 5000, // Default is 1000 (every 1s)
+ logs: true,
+ onQueueUpdate(update) {
+ setElapsedTime(Date.now() - start);
+ if (
+ update.status === 'IN_PROGRESS' ||
+ update.status === 'COMPLETED'
+ ) {
+ setLogs((update.logs || []).map((log) => log.message));
+ }
+ },
+ });
+ setResult(result);
+ } catch (error: any) {
+ setError(error);
+ } finally {
+ setLoading(false);
+ setElapsedTime(Date.now() - start);
+ }
+ // @snippet:end
+ };
+ return (
+
+
+
+ Hello fal
+
+
+
+ setPrompt(e.target.value)}
+ onBlur={(e) => setPrompt(e.target.value.trim())}
+ />
+
+
+
+
+
+
+
+
+ {image && (
+ // eslint-disable-next-line @next/next/no-img-element
+
+ )}
+
+
+
JSON Result
+
+ {`Elapsed Time (seconds): ${(elapsedTime / 1000).toFixed(2)}`}
+
+
+ {result
+ ? JSON.stringify(result, null, 2)
+ : '// result pending...'}
+
+
+
+
+
Logs
+
+ {logs.filter(Boolean).join('\n')}
+
+
+
+
+
+ );
+}
diff --git a/apps/demo-nextjs-app/index.d.ts b/apps/demo-nextjs-app-router/index.d.ts
similarity index 100%
rename from apps/demo-nextjs-app/index.d.ts
rename to apps/demo-nextjs-app-router/index.d.ts
diff --git a/apps/demo-nextjs-app/jest.config.ts b/apps/demo-nextjs-app-router/jest.config.ts
similarity index 72%
rename from apps/demo-nextjs-app/jest.config.ts
rename to apps/demo-nextjs-app-router/jest.config.ts
index f7a985d..6ff8152 100644
--- a/apps/demo-nextjs-app/jest.config.ts
+++ b/apps/demo-nextjs-app-router/jest.config.ts
@@ -1,11 +1,11 @@
/* eslint-disable */
export default {
- displayName: 'demo-nextjs-app',
+ displayName: 'demo-nextjs-app-router',
preset: '../../jest.preset.js',
transform: {
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }],
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
- coverageDirectory: '../../coverage/apps/demo-nextjs-app',
+ coverageDirectory: '../../coverage/apps/demo-nextjs-app-router',
};
diff --git a/apps/demo-nextjs-app/next-env.d.ts b/apps/demo-nextjs-app-router/next-env.d.ts
similarity index 100%
rename from apps/demo-nextjs-app/next-env.d.ts
rename to apps/demo-nextjs-app-router/next-env.d.ts
diff --git a/apps/demo-nextjs-app-router/next.config.js b/apps/demo-nextjs-app-router/next.config.js
new file mode 100644
index 0000000..007b2aa
--- /dev/null
+++ b/apps/demo-nextjs-app-router/next.config.js
@@ -0,0 +1,22 @@
+//@ts-check
+
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const { composePlugins, withNx } = require('@nx/next');
+
+/**
+ * @type {import('@nx/next/plugins/with-nx').WithNxOptions}
+ **/
+const nextConfig = {
+ nx: {
+ // Set this to true if you would like to use SVGR
+ // See: https://github.com/gregberge/svgr
+ svgr: false,
+ },
+};
+
+const plugins = [
+ // Add more Next.js plugins to this list if needed.
+ withNx,
+];
+
+module.exports = composePlugins(...plugins)(nextConfig);
diff --git a/apps/demo-nextjs-app/postcss.config.js b/apps/demo-nextjs-app-router/postcss.config.js
similarity index 100%
rename from apps/demo-nextjs-app/postcss.config.js
rename to apps/demo-nextjs-app-router/postcss.config.js
diff --git a/apps/demo-nextjs-app-router/project.json b/apps/demo-nextjs-app-router/project.json
new file mode 100644
index 0000000..5b13833
--- /dev/null
+++ b/apps/demo-nextjs-app-router/project.json
@@ -0,0 +1,68 @@
+{
+ "name": "demo-nextjs-app-router",
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
+ "sourceRoot": "apps/demo-nextjs-app-router",
+ "projectType": "application",
+ "targets": {
+ "build": {
+ "executor": "@nx/next:build",
+ "outputs": ["{options.outputPath}"],
+ "defaultConfiguration": "production",
+ "options": {
+ "outputPath": "dist/apps/demo-nextjs-app-router"
+ },
+ "configurations": {
+ "development": {
+ "outputPath": "apps/demo-nextjs-app-router"
+ },
+ "production": {}
+ }
+ },
+ "serve": {
+ "executor": "@nx/next:server",
+ "defaultConfiguration": "development",
+ "options": {
+ "buildTarget": "demo-nextjs-app-router:build",
+ "dev": true
+ },
+ "configurations": {
+ "development": {
+ "buildTarget": "demo-nextjs-app-router:build:development",
+ "dev": true
+ },
+ "production": {
+ "buildTarget": "demo-nextjs-app-router:build:production",
+ "dev": false
+ }
+ }
+ },
+ "export": {
+ "executor": "@nx/next:export",
+ "options": {
+ "buildTarget": "demo-nextjs-app-router:build:production"
+ }
+ },
+ "test": {
+ "executor": "@nx/jest:jest",
+ "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
+ "options": {
+ "jestConfig": "apps/demo-nextjs-app-router/jest.config.ts",
+ "passWithNoTests": true
+ },
+ "configurations": {
+ "ci": {
+ "ci": true,
+ "codeCoverage": true
+ }
+ }
+ },
+ "lint": {
+ "executor": "@nx/linter:eslint",
+ "outputs": ["{options.outputFile}"],
+ "options": {
+ "lintFilePatterns": ["apps/demo-nextjs-app-router/**/*.{ts,tsx,js,jsx}"]
+ }
+ }
+ },
+ "tags": []
+}
diff --git a/apps/demo-nextjs-app/public/.gitkeep b/apps/demo-nextjs-app-router/public/.gitkeep
similarity index 100%
rename from apps/demo-nextjs-app/public/.gitkeep
rename to apps/demo-nextjs-app-router/public/.gitkeep
diff --git a/apps/demo-nextjs-app-router/public/favicon.ico b/apps/demo-nextjs-app-router/public/favicon.ico
new file mode 100644
index 0000000..317ebcb
Binary files /dev/null and b/apps/demo-nextjs-app-router/public/favicon.ico differ
diff --git a/apps/demo-nextjs-app-router/tailwind.config.js b/apps/demo-nextjs-app-router/tailwind.config.js
new file mode 100644
index 0000000..948d92e
--- /dev/null
+++ b/apps/demo-nextjs-app-router/tailwind.config.js
@@ -0,0 +1,18 @@
+const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
+const { join } = require('path');
+
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: [
+ join(
+ __dirname,
+ '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}'
+ ),
+ ...createGlobPatternsForDependencies(__dirname),
+ ],
+ darkMode: 'class',
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+};
diff --git a/apps/demo-nextjs-app-router/tsconfig.json b/apps/demo-nextjs-app-router/tsconfig.json
new file mode 100644
index 0000000..19a0936
--- /dev/null
+++ b/apps/demo-nextjs-app-router/tsconfig.json
@@ -0,0 +1,36 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowJs": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "types": ["jest", "node"]
+ },
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ "**/*.js",
+ "**/*.jsx",
+ "../../apps/demo-nextjs-app-router/.next/types/**/*.ts",
+ "../../dist/apps/demo-nextjs-app-router/.next/types/**/*.ts",
+ "next-env.d.ts"
+ ],
+ "exclude": [
+ "node_modules",
+ "jest.config.ts",
+ "src/**/*.spec.ts",
+ "src/**/*.test.ts"
+ ]
+}
diff --git a/apps/demo-nextjs-app-router/tsconfig.spec.json b/apps/demo-nextjs-app-router/tsconfig.spec.json
new file mode 100644
index 0000000..214b2cc
--- /dev/null
+++ b/apps/demo-nextjs-app-router/tsconfig.spec.json
@@ -0,0 +1,21 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../dist/out-tsc",
+ "module": "commonjs",
+ "types": ["jest", "node"],
+ "jsx": "react"
+ },
+ "include": [
+ "jest.config.ts",
+ "src/**/*.test.ts",
+ "src/**/*.spec.ts",
+ "src/**/*.test.tsx",
+ "src/**/*.spec.tsx",
+ "src/**/*.test.js",
+ "src/**/*.spec.js",
+ "src/**/*.test.jsx",
+ "src/**/*.spec.jsx",
+ "src/**/*.d.ts"
+ ]
+}
diff --git a/apps/demo-nextjs-app/.eslintrc.json b/apps/demo-nextjs-page-router/.eslintrc.json
similarity index 100%
rename from apps/demo-nextjs-app/.eslintrc.json
rename to apps/demo-nextjs-page-router/.eslintrc.json
diff --git a/apps/demo-nextjs-page-router/index.d.ts b/apps/demo-nextjs-page-router/index.d.ts
new file mode 100644
index 0000000..7ba08fa
--- /dev/null
+++ b/apps/demo-nextjs-page-router/index.d.ts
@@ -0,0 +1,6 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+declare module '*.svg' {
+ const content: any;
+ export const ReactComponent: any;
+ export default content;
+}
diff --git a/apps/demo-nextjs-page-router/jest.config.ts b/apps/demo-nextjs-page-router/jest.config.ts
new file mode 100644
index 0000000..8599fdc
--- /dev/null
+++ b/apps/demo-nextjs-page-router/jest.config.ts
@@ -0,0 +1,11 @@
+/* eslint-disable */
+export default {
+ displayName: 'demo-nextjs-page-router',
+ preset: '../../jest.preset.js',
+ transform: {
+ '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
+ '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }],
+ },
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
+ coverageDirectory: '../../coverage/apps/demo-nextjs-page-router',
+};
diff --git a/apps/demo-nextjs-page-router/next-env.d.ts b/apps/demo-nextjs-page-router/next-env.d.ts
new file mode 100644
index 0000000..4f11a03
--- /dev/null
+++ b/apps/demo-nextjs-page-router/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/apps/demo-nextjs-app/next.config.js b/apps/demo-nextjs-page-router/next.config.js
similarity index 100%
rename from apps/demo-nextjs-app/next.config.js
rename to apps/demo-nextjs-page-router/next.config.js
diff --git a/apps/demo-nextjs-app/pages/_app.tsx b/apps/demo-nextjs-page-router/pages/_app.tsx
similarity index 100%
rename from apps/demo-nextjs-app/pages/_app.tsx
rename to apps/demo-nextjs-page-router/pages/_app.tsx
diff --git a/apps/demo-nextjs-app/pages/api/_fal/proxy.ts b/apps/demo-nextjs-page-router/pages/api/_fal/proxy.ts
similarity index 100%
rename from apps/demo-nextjs-app/pages/api/_fal/proxy.ts
rename to apps/demo-nextjs-page-router/pages/api/_fal/proxy.ts
diff --git a/apps/demo-nextjs-page-router/pages/index.module.css b/apps/demo-nextjs-page-router/pages/index.module.css
new file mode 100644
index 0000000..8a13e21
--- /dev/null
+++ b/apps/demo-nextjs-page-router/pages/index.module.css
@@ -0,0 +1,2 @@
+.page {
+}
diff --git a/apps/demo-nextjs-app/pages/index.tsx b/apps/demo-nextjs-page-router/pages/index.tsx
similarity index 94%
rename from apps/demo-nextjs-app/pages/index.tsx
rename to apps/demo-nextjs-page-router/pages/index.tsx
index 55a50f9..7fb7994 100644
--- a/apps/demo-nextjs-app/pages/index.tsx
+++ b/apps/demo-nextjs-page-router/pages/index.tsx
@@ -21,7 +21,11 @@ type Result = {
};
// @snippet:end
-function Error(props) {
+type ErrorProps = {
+ error: any;
+};
+
+function Error(props: ErrorProps) {
if (!props.error) {
return null;
}
@@ -64,8 +68,7 @@ export function Index() {
setElapsedTime(0);
};
- const handleOnClick = async (e) => {
- e.preventDefault();
+ const generateImage = async () => {
reset();
// @snippet:start("client.queue.subscribe")
setLoading(true);
@@ -85,12 +88,12 @@ export function Index() {
update.status === 'IN_PROGRESS' ||
update.status === 'COMPLETED'
) {
- setLogs(update.logs.map((log) => log.message));
+ setLogs((update.logs || []).map((log) => log.message));
}
},
});
setResult(result);
- } catch (error) {
+ } catch (error: any) {
setError(error);
} finally {
setLoading(false);
@@ -121,7 +124,10 @@ export function Index() {