Skip to content

Commit

Permalink
Allow SSR dynamic routes to not implement getStaticPaths (#2815)
Browse files Browse the repository at this point in the history
* Allow SSR dynamic routes to not implement getStaticPaths

* Adds a changeset

* Update based on code-review comments
  • Loading branch information
matthewp authored Mar 17, 2022
1 parent f6709d7 commit 7b9d042
Show file tree
Hide file tree
Showing 12 changed files with 81 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .changeset/spotty-houses-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Allows dynamic routes in SSR to avoid implementing getStaticPaths
2 changes: 1 addition & 1 deletion examples/ssr/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @ts-check
import { defineConfig } from 'astro/config';

export default defineConfig({
renderers: ['@astrojs/renderer-svelte'],
Expand Down
11 changes: 1 addition & 10 deletions examples/ssr/src/pages/products/[id].astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,9 @@
import Header from '../../components/Header.astro';
import Container from '../../components/Container.astro';
import AddToCart from '../../components/AddToCart.svelte';
import { getProducts, getProduct } from '../../api';
import { getProduct } from '../../api';
import '../../styles/common.css';
export async function getStaticPaths() {
const products = await getProducts();
return products.map(product => {
return {
params: { id: product.id.toString() }
}
});
}
const id = Number(Astro.request.params.id);
const product = await getProduct(id);
---
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class AstroBuilder {
origin,
routeCache: this.routeCache,
viteServer: this.viteServer,
ssr: this.config.buildOptions.experimentalSsr,
});

// Filter pages by using conditions based on their frontmatter.
Expand Down
5 changes: 3 additions & 2 deletions packages/astro/src/core/build/page-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface CollectPagesDataOptions {
origin: string;
routeCache: RouteCache;
viteServer: ViteDevServer;
ssr: boolean;
}

export interface CollectPagesDataResult {
Expand Down Expand Up @@ -109,11 +110,11 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
}

async function getStaticPathsForRoute(opts: CollectPagesDataOptions, route: RouteData): Promise<RouteCacheEntry> {
const { astroConfig, logging, routeCache, viteServer } = opts;
const { astroConfig, logging, routeCache, ssr, viteServer } = opts;
if (!viteServer) throw new Error(`vite.createServer() not called!`);
const filePath = new URL(`./${route.component}`, astroConfig.projectRoot);
const mod = (await viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance;
const result = await callGetStaticPaths(mod, route, false, logging);
const result = await callGetStaticPaths({ mod, route, isValidate: false, logging, ssr });
routeCache.set(route, result);
return result;
}
10 changes: 6 additions & 4 deletions packages/astro/src/core/render/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ interface GetParamsAndPropsOptions {
routeCache: RouteCache;
pathname: string;
logging: LogOptions;
ssr: boolean;
}

export const enum GetParamsAndPropsError {
NoMatchingStaticPath,
}

export async function getParamsAndProps(opts: GetParamsAndPropsOptions): Promise<[Params, Props] | GetParamsAndPropsError> {
const { logging, mod, route, routeCache, pathname } = opts;
const { logging, mod, route, routeCache, pathname, ssr} = opts;
// Handle dynamic routes
let params: Params = {};
let pageProps: Props;
Expand All @@ -37,18 +38,18 @@ export async function getParamsAndProps(opts: GetParamsAndPropsOptions): Promise
// TODO(fks): Can we refactor getParamsAndProps() to receive routeCacheEntry
// as a prop, and not do a live lookup/populate inside this lower function call.
if (!routeCacheEntry) {
routeCacheEntry = await callGetStaticPaths(mod, route, true, logging);
routeCacheEntry = await callGetStaticPaths({ mod, route, isValidate: true, logging, ssr });
routeCache.set(route, routeCacheEntry);
}
const matchedStaticPath = findPathItemByKey(routeCacheEntry.staticPaths, params);
if (!matchedStaticPath) {
if (!matchedStaticPath && !ssr) {
return GetParamsAndPropsError.NoMatchingStaticPath;
}
// Note: considered using Object.create(...) for performance
// Since this doesn't inherit an object's properties, this caused some odd user-facing behavior.
// Ex. console.log(Astro.props) -> {}, but console.log(Astro.props.property) -> 'expected value'
// Replaced with a simple spread as a compromise
pageProps = matchedStaticPath.props ? { ...matchedStaticPath.props } : {};
pageProps = matchedStaticPath?.props ? { ...matchedStaticPath.props } : {};
} else {
pageProps = {};
}
Expand Down Expand Up @@ -83,6 +84,7 @@ export async function render(opts: RenderOptions): Promise<{ type: 'html'; html:
route,
routeCache,
pathname,
ssr,
});

if (paramsAndPropsRes === GetParamsAndPropsError.NoMatchingStaticPath) {
Expand Down
22 changes: 16 additions & 6 deletions packages/astro/src/core/render/route-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,29 @@ function stringifyParams(params: Params) {
return JSON.stringify(params, Object.keys(params).sort());
}

export async function callGetStaticPaths(mod: ComponentInstance, route: RouteData, isValidate: boolean, logging: LogOptions): Promise<RouteCacheEntry> {
validateGetStaticPathsModule(mod);
interface CallGetStaticPathsOptions {
mod: ComponentInstance;
route: RouteData;
isValidate: boolean;
logging: LogOptions;
ssr: boolean;
}

export async function callGetStaticPaths({ isValidate, logging, mod, route, ssr}: CallGetStaticPathsOptions): Promise<RouteCacheEntry> {
validateGetStaticPathsModule(mod, { ssr });
const resultInProgress = {
rss: [] as RSS[],
};
const staticPaths: GetStaticPathsResult = await (
await mod.getStaticPaths!({

let staticPaths: GetStaticPathsResult = [];
if(mod.getStaticPaths) {
staticPaths = (await mod.getStaticPaths({
paginate: generatePaginateFunction(route),
rss: (data) => {
resultInProgress.rss.push(data);
},
})
).flat();
})).flat();
}

const keyedStaticPaths = staticPaths as GetStaticPathsResultKeyed;
keyedStaticPaths.keyed = new Map<string, GetStaticPathsItem>();
Expand Down
8 changes: 6 additions & 2 deletions packages/astro/src/core/routing/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import type { ComponentInstance, GetStaticPathsResult } from '../../@types/astro
import type { LogOptions } from '../logger';
import { warn } from '../logger.js';

interface ValidationOptions {
ssr: boolean;
}

/** Throw error for deprecated/malformed APIs */
export function validateGetStaticPathsModule(mod: ComponentInstance) {
export function validateGetStaticPathsModule(mod: ComponentInstance, { ssr }: ValidationOptions) {
if ((mod as any).createCollection) {
throw new Error(`[createCollection] deprecated. Please use getStaticPaths() instead.`);
}
if (!mod.getStaticPaths) {
if (!mod.getStaticPaths && !ssr) {
throw new Error(`[getStaticPaths] getStaticPaths() function is required. Make sure that you \`export\` the function from your component.`);
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/vite-plugin-astro-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ async function handleRequest(
routeCache,
pathname: rootRelativeUrl,
logging,
ssr: config.buildOptions.experimentalSsr,
});
if (paramsAndPropsRes === GetParamsAndPropsError.NoMatchingStaticPath) {
warn(logging, 'getStaticPaths', `Route pattern matched, but no matching static path found. (${pathname})`);
Expand Down
11 changes: 11 additions & 0 deletions packages/astro/test/fixtures/ssr-dynamic/src/pages/[id].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
const val = Number(Astro.request.params.id);
---
<html>
<head>
<title>Test app</title>
</head>
<body>
<h1>Item { val }</h1>
</body>
</html>
28 changes: 28 additions & 0 deletions packages/astro/test/ssr-dynamic.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { expect } from 'chai';
import { load as cheerioLoad } from 'cheerio';
import { loadFixture } from './test-utils.js';

// Asset bundling
describe('Dynamic pages in SSR', () => {
let fixture;

before(async () => {
fixture = await loadFixture({
projectRoot: './fixtures/ssr-dynamic/',
buildOptions: {
experimentalSsr: true,
}
});
await fixture.build();
});

it('Do not have to implement getStaticPaths', async () => {
const app = await fixture.loadSSRApp();
const request = new Request("http://example.com/123");
const route = app.match(request);
const response = await app.render(request, route);
const html = await response.text();
const $ = cheerioLoad(html);
expect($('h1').text()).to.equal('Item 123');
});
});
2 changes: 2 additions & 0 deletions packages/astro/test/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { loadConfig } from '../dist/core/config.js';
import dev from '../dist/core/dev/index.js';
import build from '../dist/core/build/index.js';
import preview from '../dist/core/preview/index.js';
import { loadApp } from '../dist/core/app/node.js';
import os from 'os';
import stripAnsi from 'strip-ansi';

Expand Down Expand Up @@ -86,6 +87,7 @@ export async function loadFixture(inlineConfig) {
inlineConfig.devOptions.port = previewServer.port; // update port for fetch
return previewServer;
},
loadSSRApp: () => loadApp(new URL('./server/', config.dist)),
readFile: (filePath) => fs.promises.readFile(new URL(filePath.replace(/^\//, ''), config.dist), 'utf8'),
readdir: (fp) => fs.promises.readdir(new URL(fp.replace(/^\//, ''), config.dist)),
clean: () => fs.promises.rm(config.dist, { maxRetries: 10, recursive: true, force: true }),
Expand Down

0 comments on commit 7b9d042

Please sign in to comment.