{"id":13809219,"url":"https://github.com/jsonmaur/phoenix-pages","last_synced_at":"2025-10-12T09:01:39.662Z","repository":{"id":161255887,"uuid":"589137756","full_name":"jsonmaur/phoenix-pages","owner":"jsonmaur","description":"Blogs, docs, and static pages in Phoenix","archived":false,"fork":false,"pushed_at":"2023-05-11T02:58:20.000Z","size":104,"stargazers_count":72,"open_issues_count":14,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-30T07:08:41.663Z","etag":null,"topics":["blog","cms","documentation","elixir","markdown","phoenix","static-site"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/phoenix_pages","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jsonmaur.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-01-15T07:18:22.000Z","updated_at":"2024-10-02T09:52:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"67ef0a1c-0f69-4df6-9680-edbfe368cf85","html_url":"https://github.com/jsonmaur/phoenix-pages","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsonmaur%2Fphoenix-pages","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsonmaur%2Fphoenix-pages/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsonmaur%2Fphoenix-pages/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsonmaur%2Fphoenix-pages/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jsonmaur","download_url":"https://codeload.github.com/jsonmaur/phoenix-pages/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245731507,"owners_count":20663200,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blog","cms","documentation","elixir","markdown","phoenix","static-site"],"created_at":"2024-08-04T01:02:10.110Z","updated_at":"2025-10-12T09:01:34.636Z","avatar_url":"https://github.com/jsonmaur.png","language":"Elixir","funding_links":[],"categories":["Static Page Generation"],"sub_categories":[],"readme":"# Phoenix Pages\n\nAdd blogs, documentation, and other static pages to Phoenix apps. This library integrates seamlessly into your router and comes with built-in support for rendering markdown with frontmatter, syntax highlighting, compile-time caching, and more.\n\n- [Installation](#installation)\n- [Getting Started](#getting-started)\n- [Frontmatter](#frontmatter)\n- [Syntax Highlighting](#syntax-highlighting)\n- [Index Pages](#index-pages)\n- [Defining Paths](#defining-paths)\n- [Extended Markdown](#extended-markdown)\n- [Local Development](#local-development)\n\n## Installation\n\n```elixir\ndef deps do\n  [\n    {:phoenix_pages, \"~\u003e 0.1\"}\n  ]\nend\n```\n\nThe recommended way to install into your Phoenix application is to add this to your `router` function in `lib/myapp_web.ex`, replacing `myapp` with the name of your application:\n\n```elixir\ndef router do\n  quote do\n    use Phoenix.Router, helpers: false\n    use PhoenixPages, otp_app: :myapp\n\n    # ...\n  end\nend\n```\n\n## Getting Started\n\nNow you can add a new route using the [`pages/4`](https://hexdocs.pm/phoenix_pages/PhoenixPages.html#pages/4) macro:\n\n```elixir\nscope \"/\", MyAppWeb do\n  pipe_through :browser\n\n  get \"/\", PageController, :home\n  pages \"/:page\", PageController, :show, from: \"priv/pages/**/*.md\"\nend\n```\n\nThis will read all the markdown files from `priv/pages` and create a new GET route for each one. The `:page` segment will be replaced with the path and filename (without the extension) relative to the base directory (see [Defining Paths](#defining-paths)).\n\nYou'll also need to add the `:show` handler to `lib/myapp_web/controllers/page_controller.ex`:\n\n```elixir\ndefmodule MyAppWeb.PageController do\n  use MyAppWeb, :controller\n\n  # ...\n\n  def show(conn, _params) do\n    render(conn, \"show.html\")\n  end\nend\n```\n\nLastly, add a template at `lib/myapp_web/controllers/page_html/show.html.heex`. The page's rendered markdown will be available in the `inner_content` assign:\n\n```heex\n\u003cmain\u003e\n  \u003c%= @inner_content %\u003e\n\u003c/main\u003e\n```\n\nThat's it! Now try creating a file at `priv/pages/hello.md` and visiting `/hello`.\n\n### Formatting\n\nTo prevent `mix format` from adding parenthesis to the `pages` macro similar to the other Phoenix Router macros, add `:phoenix_pages` to `.formatter.exs`:\n\n```elixir\n[\n  import_deps: [:ecto, :ecto_sql, :phoenix, :phoenix_pages]\n]\n```\n\n## Frontmatter\n\nFrontmatter allows page-specific variables to be included at the top of a markdown file using the YAML format. If you're setting frontmatter variables (which is optional), they must be the first thing in the file and must be set between triple-dashed lines:\n\n```markdown\n---\ntitle: Hello World\n---\nLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut\nlabore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris\nnisi ut aliquip ex ea commodo consequat.\n```\n\nTo specify which frontmatter values are expected in each page, set the `attrs` option:\n\n```elixir\npages \"/:page\", PageController, :show,\n  from: \"priv/pages/**/*.md\",\n  attrs: [:title, author: nil]\n```\n\nAtom values will be considered required, and a compilation error will be thrown if missing from any of the pages. Key-values must come last in the list, and will be considered optional by defining a default value. Any frontmatter values not defined in the attributes list will be silently discarded.\n\nValid attribute values will be available in the assigns:\n\n```heex\n\u003cmain\u003e\n  \u003ch1\u003e\u003c%= @title %\u003e\u003c/h1\u003e\n  \u003ch2 :if={@author}\u003e\u003c%= @author %\u003e\u003c/h2\u003e\n\n  \u003c%= @inner_content %\u003e\n\u003c/main\u003e\n```\n\n## Syntax Highlighting\n\nPhoenix Pages uses the [Makeup](https://github.com/elixir-makeup/makeup) project for syntax highlighting. To enable, add a lexer for your specific language(s) to the project dependencies. Phoenix Pages will pick up the new dependency and start highlighting your code blocks without any further configuration. No lexers are included by default.\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cb\u003eLexers\u003c/b\u003e\u003c/summary\u003e\n\n  \u003cul\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/elixir-makeup/makeup_c\"\u003eC\u003c/a\u003e - \u003ccode\u003e`{:makeup_c, \"~\u003e 0.0\"}`\u003c/code\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/elixir-makeup/makeup_diff\"\u003eDiff\u003c/a\u003e - \u003ccode\u003e`{:makeup_diff, \"~\u003e 0.0\"}`\u003c/code\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/elixir-makeup/makeup_elixir\"\u003eElixir\u003c/a\u003e - \u003ccode\u003e`{:makeup_elixir, \"~\u003e 0.0\"}`\u003c/code\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/elixir-makeup/makeup_erlang\"\u003eErlang\u003c/a\u003e - \u003ccode\u003e`{:makeup_erlang, \"~\u003e 0.0\"}`\u003c/code\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/Billzabob/makeup_graphql\"\u003eGraphQL\u003c/a\u003e - \u003ccode\u003e`{:makeup_graphql, \"~\u003e 0.0\"}`\u003c/code\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/elixir-makeup/makeup_eex\"\u003e(H)EEx\u003c/a\u003e - \u003ccode\u003e`{:makeup_eex, \"~\u003e 0.0\"}`\u003c/code\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/elixir-makeup/makeup_html\"\u003eHTML\u003c/a\u003e - \u003ccode\u003e`{:makeup_html, \"~\u003e 0.0\"}`\u003c/code\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/maartenvanvliet/makeup_js\"\u003eJavascript\u003c/a\u003e - \u003ccode\u003e`{:makeup_js, \"~\u003e 0.0\"}`\u003c/code\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/elixir-makeup/makeup_json\"\u003eJSON\u003c/a\u003e - \u003ccode\u003e`{:makeup_json, \"~\u003e 0.0\"}`\u003c/code\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/dottorblaster/makeup_rust\"\u003eRust\u003c/a\u003e - \u003ccode\u003e`{:makeup_rust, \"~\u003e 0.0\"}`\u003c/code\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://github.com/Billzabob/makeup_sql\"\u003eSQL\u003c/a\u003e - \u003ccode\u003e`{:makeup_sql, \"~\u003e 0.0\"}`\u003c/code\u003e\u003c/li\u003e\n  \u003c/ul\u003e\n\u003c/details\u003e\n\nIf your language of choice isn't supported, consider [writing a new Makeup lexer](https://github.com/elixir-makeup/makeup/blob/master/CONTRIBUTING.md#writing-a-new-lexer) to contribute to the community. Otherwise, you can use a JS-based syntax highlighter such as [highlight.js](https://highlightjs.org) by setting `code_class_prefix: \"language-\"` and `syntax_highlighting: false` in [`render_options`](https://hexdocs.pm/phoenix_pages/PhoenixPages.html#pages/4-options).\n\nNext, import a theme listed below into your CSS bundle. The specifics of doing this highly depend on your CSS configuration, but a few examples are included below. In most cases, you will need to import `phoenix_pages/css/monokai.css` (or whatever theme you choose) into your bundle and ensure `deps` is included as a vendor directory.\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cb\u003eThemes\u003c/b\u003e\u003c/summary\u003e\n\n  \u003cul\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#abap\"\u003eabap\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#algol\"\u003ealgol\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#algol_nu\"\u003ealgol_nu\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#arduino\"\u003earduino\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#autumn\"\u003eautumn\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#black_white\"\u003eblack_white\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#borland\"\u003eborland\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#colorful\"\u003ecolorful\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#default\"\u003edefault\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#emacs\"\u003eemacs\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#friendly\"\u003efriendly\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#fruity\"\u003efruity\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#igor\"\u003eigor\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#lovelace\"\u003elovelace\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#manni\"\u003emanni\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#monokai\"\u003emonokai\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#murphy\"\u003emurphy\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#native\"\u003enative\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#paraiso_dark\"\u003eparaiso_dark\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#paraiso_light\"\u003eparaiso_light\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#pastie\"\u003epastie\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#perldoc\"\u003eperldoc\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#rainbow_dash\"\u003erainbow_dash\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#rrt\"\u003errt\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#samba\"\u003esamba\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#tango\"\u003etango\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#trac\"\u003etrac\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#vim\"\u003evim\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#visual_studio\"\u003evisual_studio\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"https://elixir-makeup.github.io/makeup_demo/elixir.html#xcode\"\u003excode\u003c/a\u003e\u003c/li\u003e\n  \u003c/ul\u003e\n\u003c/details\u003e\n\n### ESBuild Example\n\nUsing the [ESBuild installer](https://github.com/phoenixframework/esbuild), add the `env` option to `config/config.exs`:\n\n```elixir\nconfig :esbuild,\n  version: \"0.17.18\",\n  default: [\n    cd: Path.expand(\"../assets\", __DIR__),\n    env: %{\"NODE_PATH\" =\u003e Path.expand(\"../deps\", __DIR__)},\n    args: ~w(--bundle --outdir=../priv/static/assets js/app.js)\n  ]\n```\n\nThen in `app.js`:\n\n```javascript\nimport \"phoenix_pages/css/monokai.css\";\n```\n\n### Sass Example\n\nUsing the [Sass installer](https://github.com/CargoSense/dart_sass), add the `--load-path` flag to `config/config.exs`:\n\n```elixir\nconfig :dart_sass,\n  version: \"1.62.0\",\n  default: [\n    cd: Path.expand(\"../assets\", __DIR__),\n    args: ~w(--load-path=../deps css/app.scss ../priv/static/assets/app.css)\n  ]\n```\n\nThen in `app.scss`:\n\n```scss\n@import \"phoenix_pages/css/monokai\";\n```\n\n### Tailwind Example\n\nInstall the `postcss-import` plugin as described [here](https://tailwindcss.com/docs/using-with-preprocessors#build-time-imports) and add the following to `assets/postcss.config.js`:\n\n```javascript\nmodule.exports = {\n  plugins: {\n    \"postcss-import\": {}\n  }\n}\n```\n\nThen in `app.css`:\n\n```css\n@import \"../../deps/phoenix_pages/css/monokai\";\n```\n\n## Index Pages\n\nTo create an index page with links to all the other pages, create a normal GET route and use the [`id`](https://hexdocs.pm/phoenix_pages/PhoenixPages.html#pages/4-options) option alongside [`get_pages/1`](https://hexdocs.pm/phoenix_pages/PhoenixPages.html#c:get_pages/1) and [`get_pages!/1`](https://hexdocs.pm/phoenix_pages/PhoenixPages.html#c:get_pages!/1) in your router:\n\n```elixir\nget \"/blog\", BlogController, :index\n\npages \"/blog/:page\", BlogController, :show,\n  id: :blog,\n  from: \"priv/blog/**/*.md\",\n  attrs: [:title, :author, :date]\n```\n\n```elixir\ndefmodule MyAppWeb.BlogController do\n  use MyAppWeb, :controller\n\n  def index(conn, _params) do\n    pages = MyAppWeb.Router.get_pages!(:blog)\n\n    conn\n    |\u003e assign(:pages, pages)\n    |\u003e render(\"index.html\")\n  end\n\n  def show(conn, _params) do\n    render(conn, \"show.html\")\n  end\nend\n```\n\n```heex\n\u003c.link :for={page \u003c- @pages} navigate={page.path}\u003e\n  \u003c%= page.assigns.title %\u003e\n\u003c/.link\u003e\n```\n\nAll the page files are read and cached during compilation, so the `get_pages` functions will not actually read anything from the filesystem—making them very performant.\n\n### Sorting\n\nThe pages returned from the `get_pages` functions will be sorted by filename. If you want to specify a different order during compilation rather than in the controller on every page load, use the [`sort`](https://hexdocs.pm/phoenix_pages/PhoenixPages.html#pages/4-options) option:\n\n```elixir\npages \"/blog/:page\", BlogController, :show,\n  id: :blog,\n  from: \"priv/blog/**/*.md\",\n  attrs: [:title, :author, :date],\n  sort: {:date, :desc}\n```\n\nAny attribute value from the frontmatter can be defined as the sort value.\n\n## Defining Paths\n\nWhen defining the pages path, the `:page` segment will be replaced for each generated page **during compilation** with the values derived from `**` and `*`. This is different than segments in regular routes, which are parsed **during runtime** into the `params` attribute of the controller function.\n\nFor example, let's say you have the following file structure:\n\n```\n┌── priv/\n│  ┌── pages/\n│  │  ┌── foo.md\n│  │  ├── bar/\n│  │  │  ┌── baz.md\n```\n\nDefining `pages \"/:page\", from: \"priv/pages/**/*.md\"` in your router will create two routes: `get \"/foo\"` and `get \"/bar/baz\"`. You can even put the `:page` segment somewhere else in the path, such as `/blog/:page`, and it will work as expected creating `get \"/blog/foo\"` and `get \"/blog/bar/baz\"`.\n\n### Capture Groups\n\nFor complex scenarios, you have the option of using capture group variables instead of the `:page` segment.\n\nLet's say you have the same file structure as above, but don't want the `baz` path to be nested under `/bar`. You could define `pages \"/$2\", from: \"priv/pages/**/*.md\"`, using `$2` instead of `:page`. This will create two routes: `get \"/foo\"` and `get \"/bar\"`.\n\nCapture group variables will contain the value of the `**` and `*` chunks in order, starting at `$1`. Keep in mind that `**` will match all files and zero or more directories and subdirectories, and `*` will match any number of characters up to the end of the filename, the next dot, or the next slash.\n\nFor more info on the wildcard patterns, check out [Path.wildcard/2](https://hexdocs.pm/elixir/1.13/Path.html#wildcard/2).\n\n## Extended Markdown\n\nIn addition to the customizable [markdown options](https://hexdocs.pm/phoenix_pages/PhoenixPages.html#pages/4-options), markdown rendering also supports IAL attributes by default. Meaning you can add HTML attributes to any block-level element using the syntax `{:attr}`.\n\nFor example, to create a rendered output of `\u003ch1 class=\"foobar\"\u003eHeader\u003c/h1\u003e`:\n\n```markdown\n# Header{:.foobar}\n```\n\nAttributes can be one of the following:\n\n- `{:#id}` to define an ID\n- `{:.className}` to define a class name\n- `{:name=value}`, `{:name=\"value\"}`, or `{:name='value'}` to define any other attribute\n\nTo define multiple attributes, separate them with spaces: `{:#id name=value}`.\n\n## Local Development\n\nIf you add, remove, or change pages while running `mix phx.server`, they will automatically be replaced in the cache and you don't have to restart for them to take effect. To live reload when a page changes, add to the patterns list of the Endpoint config in `config/dev.exs`:\n\n```elixir\nconfig :myapp, MyAppWeb.Endpoint,\n  live_reload: [\n    patterns: [\n      # ...\n      ~r\"priv/pages/.*(md)$\",\n      # ...\n    ]\n  ]\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsonmaur%2Fphoenix-pages","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjsonmaur%2Fphoenix-pages","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsonmaur%2Fphoenix-pages/lists"}