All features up till now describes how this template implements a Markdown powered Razor Pages .NET application, where this template
differs in its published output, where instead of a .NET App deployed to a VM or App server it generates static *.html
files that's
bundled together with /wwwroot
static assets in the /dist
folder with:
npm run prerender
That can then be previewed by launching a HTTP Server from the /dist
folder with the built-in npm script:
npm run serve
That runs npx http-server on http://localhost:8080
that you can open in a browser to preview the published version of your
site as it would be when hosted on a CDN.
Static Razor Pages​
The static generation functionality works by scanning all your Razor Pages and prerendering the pages with prerendering instructions.
Pages with Static Routes​
Pages with static routes can be marked to be prerendered by annotating it with the [RenderStatic]
attribute as done in
About.cshtml:
@page "/about"
@attribute [RenderStatic]
Which saves the pre-rendered page using the pages route with a .html suffix, e.g: /{@page route}.html
whilst pages with static
routes with a trailing /
are saved to /{@page route}/index.html
:
@page "/vue/"
@attribute [RenderStatic]
Explicit generated paths​
To keep the generated pages in-sync with using the same routes as your Razor Pages in development it's recommended to use the implied rendered paths, but if preferred you can specify which path the page should render to instead with:
@page "/vue/"
@attribute [RenderStatic("/vue/index.html")]
Pages with Dynamic Routes​
Prerendering dynamic pages follows Next.js getStaticProps
convention which you can implement using IRenderStatic<PageModel>
by returning a Page Model for each page that should be generated
as done in Vue/Page.cshtml and
Page.cshtml:
@page "/{slug}"
@model MyApp.Page
@implements IRenderStatic<MyApp.Page>
@functions {
public List<Page> GetStaticProps(RenderContext ctx)
{
var markdown = ctx.Resolve<MarkdownPages>();
return markdown.GetVisiblePages().Map(page => new Page { Slug = page.Slug! });
}
}
...
In this case it returns a Page Model for every Visible markdown page in
/_pages that ends up rendering the following pages in /dist
:
/what-is-razor-press.html
/structure.html
/privacy.html
Limitations​
The primary limitations for developing statically generated Apps is that a snapshot of entire App is generated at deployment, which prohibits being able to render different content per request, e.g. for Authenticated users which would instead require executing custom JavaScript after the page loads to dynamically alter the page's initial content.
Otherwise in practice you'll be able develop your Razor Pages utilizing Razor's full feature-set, the primary concessions stem from Pages being executed in a static context which prohibits pages from returning dynamic content per request, instead any "different views" should be maintained in separate pages.
No QueryString Params​
As the generated pages should adopt the same routes as your Razor Pages you'll need to avoid relying on ?QueryString params and instead capture all required parameters for a page in its @page route as done for:
@page "/posts/author/{slug}"
@model AuthorModel
@inject MarkdownBlog Blog
@implements IRenderStatic<AuthorModel>
@functions {
public List<AuthorModel> GetStaticProps(RenderContext ctx) => ctx.Resolve<MarkdownBlog>()
.AuthorSlugMap.Keys.Map(x => new AuthorModel { Slug = x });
}
...
Which lists all posts by an Author, e.g: /posts/author/lucy-bates, likewise required for:
@page "/posts/tagged/{slug}"
@model TaggedModel
@inject MarkdownBlog Blog
@implements IRenderStatic<TaggedModel>
@functions {
public List<TaggedModel> GetStaticProps(RenderContext ctx) => ctx.Resolve<MarkdownBlog>()
.TagSlugMap.Keys.Map(x => new TaggedModel { Slug = x });
}
...
Which lists all related posts with a specific tag, e.g: /posts/tagged/markdown, and for:
@page "/posts/year/{year}"
@model YearModel
@inject MarkdownBlog Blog
@implements IRenderStatic<YearModel>
@functions {
public List<YearModel> GetStaticProps(RenderContext ctx) => ctx.Resolve<MarkdownBlog>()
.VisiblePosts.Select(x => x.Date.GetValueOrDefault().Year)
.Distinct().Map(x => new YearModel { Year = x });
}
...
Which lists all posts published in a specific year, e.g: /posts/year/2023.
Conceivably these "different views" could've been implemented by the same page with different ?author
, ?tag
and ?year
QueryString params, but need to instead be extracted into different pages to support its statically generated *.html
outputs.
Prerendering Task​
The prerender AppTask that pre-renders the entire website is also registered in Configure.Ssg.cs:
.ConfigureAppHost(afterAppHostInit: appHost =>
{
// prerender with: `$ npm run prerender`
AppTasks.Register("prerender", args =>
{
appHost.Resolve<MarkdownMeta>().RenderToAsync(
metaDir: appHost.ContentRootDirectory.RealPath.CombineWith("wwwroot/meta"),
baseUrl: HtmlHelpers.ToAbsoluteContentUrl("")).GetAwaiter().GetResult();
var distDir = appHost.ContentRootDirectory.RealPath.CombineWith("dist");
if (Directory.Exists(distDir))
FileSystemVirtualFiles.DeleteDirectory(distDir);
FileSystemVirtualFiles.CopyAll(
new DirectoryInfo(appHost.ContentRootDirectory.RealPath.CombineWith("wwwroot")),
new DirectoryInfo(distDir));
// Render .html redirect files
RazorSsg.PrerenderRedirectsAsync(appHost.ContentRootDirectory.GetFile("redirects.json"), distDir)
.GetAwaiter().GetResult();
var razorFiles = appHost.VirtualFiles.GetAllMatchingFiles("*.cshtml");
RazorSsg.PrerenderAsync(appHost, razorFiles, distDir).GetAwaiter().GetResult();
});
});
//...
Which we can see:
- Deletes
/dist
folder - Copies
/wwwroot
contents into/dist
- Generates redirect
.html
files for all paths inredirects.json
- Passes all App's Razor
*.cshtml
files toRazorSsg
to do the pre-rendering
Where it processes all pages with [RenderStatic]
and IRenderStatic<PageModel>
prerendering instructions to the
specified /dist
folder.
Previewing prerendered site​
To preview your SSG website, run the prerendered task with:
npm run prerender
Which renders your site to /dist
which you can run a HTTP Server from with:
npm run serve
That you can preview with your browser at http://localhost:8080
.